fix: marked兼容、保存竞态、关闭死锁、XSS防护、行号虚拟化、CSP加固
This commit is contained in:
@@ -8,6 +8,7 @@ let pendingFilePath = null;
|
||||
let fileWatcher = null;
|
||||
let closeTimeout = null;
|
||||
let isClosing = false; // Prevent re-entrant close
|
||||
let isSelfWriting = false; // Skip fs.watch events triggered by our own writes
|
||||
|
||||
// ===== Single Instance Lock =====
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
@@ -81,7 +82,15 @@ function createWindow() {
|
||||
isClosing = true;
|
||||
e.preventDefault();
|
||||
try {
|
||||
mainWindow.webContents.send('window:confirmClose');
|
||||
// 检查 webContents 是否仍可用
|
||||
if (!mainWindow.webContents.isDestroyed()) {
|
||||
mainWindow.webContents.send('window:confirmClose');
|
||||
} else {
|
||||
// 渲染进程已销毁,直接关闭
|
||||
mainWindow.removeAllListeners('close');
|
||||
mainWindow.close();
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
mainWindow.removeAllListeners('close');
|
||||
mainWindow.close();
|
||||
@@ -89,6 +98,7 @@ function createWindow() {
|
||||
}
|
||||
// 5s safety timeout in case renderer is unresponsive
|
||||
closeTimeout = setTimeout(() => {
|
||||
closeTimeout = null;
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.removeAllListeners('close');
|
||||
mainWindow.close();
|
||||
@@ -109,6 +119,8 @@ function startWatching(filePath) {
|
||||
try {
|
||||
fileWatcher = fs.watch(filePath, (eventType) => {
|
||||
if (eventType === 'change' && mainWindow && !mainWindow.isDestroyed()) {
|
||||
// 跳过自身写入触发的 change 事件
|
||||
if (isSelfWriting) return;
|
||||
mainWindow.webContents.send('file:externallyModified', filePath);
|
||||
}
|
||||
});
|
||||
@@ -174,13 +186,22 @@ function switchActiveFile(filePath) {
|
||||
function saveToPath(filePath, content) {
|
||||
stopWatching();
|
||||
try {
|
||||
isSelfWriting = true;
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
// 写入后短暂延迟再恢复监听,让 fs.watch 错过自身写入的事件
|
||||
isSelfWriting = false;
|
||||
} catch (err) {
|
||||
isSelfWriting = false;
|
||||
startWatching(activeFilePath); // Restore watcher on failure
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
activeFilePath = filePath;
|
||||
startWatching(filePath);
|
||||
// 延迟 300ms 重新监听,确保文件系统的 change 事件已过期
|
||||
setTimeout(() => {
|
||||
if (activeFilePath === filePath) {
|
||||
startWatching(filePath);
|
||||
}
|
||||
}, 300);
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`);
|
||||
}
|
||||
@@ -283,11 +304,11 @@ ipcMain.handle('window:forceClose', () => {
|
||||
clearTimeout(closeTimeout);
|
||||
closeTimeout = null;
|
||||
}
|
||||
// 窗口已销毁则无需操作(超时兜底可能已经关闭了窗口)
|
||||
if (!mainWindow || mainWindow.isDestroyed()) return;
|
||||
stopWatching();
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.removeAllListeners('close');
|
||||
mainWindow.close();
|
||||
}
|
||||
mainWindow.removeAllListeners('close');
|
||||
mainWindow.close();
|
||||
});
|
||||
|
||||
ipcMain.handle('window:cancelClose', () => {
|
||||
|
||||
Reference in New Issue
Block a user