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:
@@ -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 duplicate‐handler 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 };
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user