Add GPU error handling and performance optimizations

Introduces a comprehensive GPU configuration and fallback system to resolve GPU process launch failures (Error 18), including new modules for GPU management and performance monitoring. Adds a GPU diagnostics HTML page, optimized CSS for rendering, and a diagnostic startup script. Updates main and preload scripts for improved stability, async file operations, and enhanced API exposure. Site history and bookmarks handling are optimized for performance and reliability.
This commit is contained in:
2025-07-26 14:38:05 +12:00
parent 0b61f86dd4
commit fbd9ba8a1b
10 changed files with 1081 additions and 59 deletions
+181 -47
View File
@@ -1,19 +1,24 @@
const { app, BrowserWindow, ipcMain, session, screen, shell } = require('electron');
const fs = require('fs');
const path = require('path');
const PerformanceMonitor = require('./performance-monitor');
const GPUFallback = require('./gpu-fallback');
const GPUConfig = require('./gpu-config');
app.commandLine.appendSwitch('ignore-gpu-blacklist');
app.commandLine.appendSwitch('enable-gpu-rasterization');
app.commandLine.appendSwitch('enable-zero-copy');
app.commandLine.appendSwitch('enable-native-gpu-memory-buffers');
app.commandLine.appendSwitch('ignore-gpu-blocklist');
app.commandLine.appendSwitch('enable-accelerated-video-decode');
app.commandLine.appendSwitch('enable-features', 'VaapiVideoDecoder,CanvasOopRasterization');
app.commandLine.appendSwitch('no-sandbox'); // Optional, for some setups
// Initialize performance monitoring and GPU management
const perfMonitor = new PerformanceMonitor();
const gpuFallback = new GPUFallback();
const gpuConfig = new GPUConfig();
// Configure GPU settings before app is ready
gpuConfig.configure();
// Set a custom application name
app.setName('Nebula');
// Setup GPU crash handling
gpuFallback.setupCrashHandling();
// --- clear any prior registrations to prevent duplicatehandler errors ---
ipcMain.removeHandler('window-minimize');
ipcMain.removeHandler('window-maximize');
@@ -30,12 +35,19 @@ function createWindow(startUrl) {
resizable: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: true, // was false
nodeIntegration: false, // Security & performance improvement
contextIsolation: true,
webviewTag: true,
enableRemoteModule: true, // Enable the remote module
nodeIntegrationInSubFrames: true, // ← allow require() inside your <webview>
nativeWindowOpen: false // Prevent Electron from creating new windows
enableRemoteModule: false, // Deprecated and slow
nodeIntegrationInSubFrames: false, // Security & performance
nativeWindowOpen: false,
spellcheck: false, // Disable if not needed
webSecurity: true,
allowRunningInsecureContent: false,
experimentalFeatures: false,
offscreen: false, // Ensure on-screen rendering for GPU
enableWebSQL: false, // Disable deprecated features
plugins: false // Disable plugins that might interfere with GPU
},
fullscreen: false,
autoHideMenuBar: true,
@@ -82,18 +94,34 @@ function createWindow(startUrl) {
// ensure all embedded <webview> tags also use the same window
win.webContents.on('did-attach-webview', (event, webContents) => {
// Set up webview with preload script to provide electronAPI
// Set up webview with preload script to provide electronAPI - fixed injection
webContents.on('dom-ready', () => {
// Simpler, more reliable API injection that doesn't require cloning
webContents.executeJavaScript(`
window.electronAPI = {
invoke: (channel, ...args) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = require('electron');
ipcRenderer.invoke(channel, ...args).then(resolve).catch(reject);
});
}
};
`);
if (!window.electronAPI) {
// Create a simple bridge without complex objects
window.electronAPI = {
invoke: function(channel) {
const args = Array.prototype.slice.call(arguments, 1);
return new Promise(function(resolve, reject) {
try {
const ipcRenderer = require('electron').ipcRenderer;
ipcRenderer.invoke(channel, ...args).then(resolve).catch(reject);
} catch (err) {
reject(err);
}
});
}
};
console.log('electronAPI injected successfully');
}
`).catch(err => {
console.error('Failed to inject electronAPI:', err);
// Fallback: inject minimal API
webContents.executeJavaScript(`
window.electronAPI = { invoke: function() { return Promise.resolve(); } };
`).catch(() => {});
});
});
// intercept window.open() inside webview
@@ -145,35 +173,68 @@ function createWindow(startUrl) {
// Set default zoom to 100%
const zoomFactor = 1.0;
const loadStartTime = Date.now();
win.webContents.on('did-finish-load', () => {
win.webContents.setZoomFactor(zoomFactor);
// Track load time for performance monitoring
const loadTime = Date.now() - loadStartTime;
perfMonitor.trackLoadTime(win.webContents.getURL(), loadTime);
});
// record site and search history on every navigation
// Debounced history recording to prevent excessive I/O
let historyTimeout;
const recordHistory = async (fileName, entry) => {
if (fileName === 'site-history.json') {
// Save to both file and send to renderer
const filePath = path.join(__dirname, fileName);
let data = [];
try { data = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch {}
if (data[0] !== entry) {
data.unshift(entry);
if (data.length > 100) data.pop();
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
}
// Also send to renderer for localStorage
win.webContents.send('record-site-history', entry);
} else {
// Keep search history in JSON file for now
const filePath = path.join(__dirname, fileName);
let data = [];
try { data = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch {}
if (data[0] !== entry) {
data.unshift(entry);
if (data.length > 100) data.pop();
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
}
// Clear existing timeout
if (historyTimeout) {
clearTimeout(historyTimeout);
}
// Debounce history recording by 500ms
historyTimeout = setTimeout(async () => {
if (fileName === 'site-history.json') {
// Save to both file and send to renderer
const filePath = path.join(__dirname, fileName);
let data = [];
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
data = JSON.parse(fileContent);
} catch {}
if (data[0] !== entry) {
data.unshift(entry);
if (data.length > 100) data.pop();
// Use async file operations to prevent blocking
try {
await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2));
} catch (err) {
console.error('Error writing site history:', err);
}
}
// Also send to renderer for localStorage
win.webContents.send('record-site-history', entry);
} else {
// Keep search history in JSON file for now
const filePath = path.join(__dirname, fileName);
let data = [];
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
data = JSON.parse(fileContent);
} catch {}
if (data[0] !== entry) {
data.unshift(entry);
if (data.length > 100) data.pop();
try {
await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2));
} catch (err) {
console.error('Error writing search history:', err);
}
}
}
}, 500);
};
win.webContents.on('did-navigate', (event, url) => {
@@ -187,7 +248,40 @@ function createWindow(startUrl) {
}
// This method will be called when Electron has finished initialization
app.whenReady().then(() => {
app.whenReady().then(async () => {
// Check GPU status and handle errors
const gpuStatus = await gpuConfig.checkGPUStatus();
console.log('GPU Configuration Results:');
console.log('- GPU Status:', gpuStatus);
console.log('- Recommendations:', gpuConfig.getRecommendations());
// Handle GPU process crashes
app.on('gpu-process-crashed', (event, killed) => {
console.warn('GPU process crashed, killed:', killed);
if (!killed) {
console.log('Attempting to recover GPU process...');
}
});
// Optimize session settings for performance
const ses = session.defaultSession;
try {
// Enable request/response caching
ses.webRequest.onBeforeSendHeaders((details, callback) => {
details.requestHeaders['Cache-Control'] = 'max-age=3600';
callback({ requestHeaders: details.requestHeaders });
});
// Skip preload registration as it's handled in window options
console.log('Session configured successfully');
} catch (err) {
console.error('Session setup error:', err);
}
// Start performance monitoring
perfMonitor.start();
createWindow();
if (process.platform === 'darwin') {
// Set macOS dock icon using an icns file for proper display.
@@ -359,3 +453,43 @@ ipcMain.handle('save-site-history-entry', async (event, url) => {
return false;
}
});
// Add performance monitoring IPC handlers
ipcMain.handle('get-performance-report', () => {
return perfMonitor.getReport();
});
ipcMain.handle('force-gc', () => {
perfMonitor.forceGC();
return true;
});
// GPU diagnostics handler
ipcMain.handle('get-gpu-info', async () => {
try {
const gpuStatus = await gpuConfig.checkGPUStatus();
const fallbackStatus = gpuFallback.getStatus();
const recommendations = gpuConfig.getRecommendations();
return {
...gpuStatus,
fallbackStatus: fallbackStatus,
recommendations: recommendations,
isOptimized: gpuStatus.isSupported && !fallbackStatus.fallbackLevel
};
} catch (err) {
console.error('Error getting GPU info:', err);
return { error: err.message, isSupported: false };
}
});
// Force GPU fallback handler
ipcMain.handle('apply-gpu-fallback', (event, level) => {
try {
gpuFallback.applyFallback(level);
return { success: true, level: level };
} catch (err) {
console.error('Error applying GPU fallback:', err);
return { error: err.message };
}
});