Add plugin system with sample plugin and settings UI

Introduces a plugin architecture, including a PluginManager, plugin loading in main and renderer processes, and a sample plugin demonstrating menu, IPC, and context menu contributions. Adds a Plugins tab to the settings UI for managing plugins (enable/disable, reload), and updates preload.js to load renderer preloads from plugins. Documentation for plugin development is included in README-PLUGINS.md.
This commit is contained in:
2025-09-08 19:10:05 +12:00
parent 62810fcb89
commit e228ca6317
11 changed files with 576 additions and 2 deletions
+38 -1
View File
@@ -6,11 +6,13 @@ const os = require('os');
const PerformanceMonitor = require('./performance-monitor');
const GPUFallback = require('./gpu-fallback');
const GPUConfig = require('./gpu-config');
const PluginManager = require('./plugin-manager');
// Initialize performance monitoring and GPU management
const perfMonitor = new PerformanceMonitor();
const gpuFallback = new GPUFallback();
const gpuConfig = new GPUConfig();
const pluginManager = new PluginManager();
// Try to enable WebAuthn/platform authenticator features early.
// This helps Chromium expose platform authenticators (Touch ID / built-in) where supported.
@@ -241,6 +243,9 @@ function createWindow(startUrl) {
} catch (e) {
console.warn('WebAuthn diagnostic injection skipped:', e);
}
// After the first load, let plugins know a window exists
try { pluginManager.emit('window-created', win); } catch {}
});
// Renderer manages history; no main-process recording here
@@ -304,6 +309,14 @@ function configureSessionsAsync() {
app.whenReady().then(() => {
const t0 = performance.now();
createWindow();
// Initialize user plugins after app ready
try {
pluginManager.ensureUserPluginsDir();
pluginManager.loadAll();
pluginManager.emit('app-ready');
} catch (e) {
console.error('[Plugins] initialization error:', e);
}
console.log('[Startup] createWindow invoked in', (performance.now() - t0).toFixed(1), 'ms after app.whenReady');
// Handle GPU process crashes (still register early)
@@ -323,6 +336,10 @@ app.whenReady().then(() => {
const defSes = session.defaultSession;
if (mainSes) registerDownloadHandling(mainSes);
if (defSes && defSes !== mainSes) registerDownloadHandling(defSes);
// Allow plugins to attach webRequest hooks
if (mainSes) pluginManager.applyWebRequestHandlers(mainSes);
if (defSes) pluginManager.applyWebRequestHandlers(defSes);
pluginManager.emit('session-configured', { mainSes, defSes });
} catch (e) {
console.warn('Failed to register download handlers:', e);
}
@@ -750,7 +767,9 @@ function buildAndShowContextMenu(sender, params = {}) {
}
});
const menu = Menu.buildFromTemplate(template);
// Allow plugins to customize/append context menu
try { pluginManager.applyContextMenuContrib(template, params, sender); } catch {}
const menu = Menu.buildFromTemplate(template);
const win = BrowserWindow.fromWebContents(embedder);
if (win) menu.popup({ window: win });
} catch (err) {
@@ -763,6 +782,24 @@ ipcMain.handle('show-context-menu', (event, params = {}) => {
buildAndShowContextMenu(event.sender, params);
});
// Plugins: expose renderer preload list
ipcMain.handle('plugins-get-renderer-preloads', () => {
try { return pluginManager.getRendererPreloads(); } catch { return []; }
});
// Plugins: management IPC for settings UI
ipcMain.handle('plugins-list', () => pluginManager.discoverPlugins());
ipcMain.handle('plugins-set-enabled', async (_e, { id, enabled }) => {
const ok = await pluginManager.setEnabled(id, enabled);
// Reload to apply enable/disable (requires app reload for renderer preloads)
pluginManager.reload();
return ok;
});
ipcMain.handle('plugins-reload', (_e, { id } = {}) => {
pluginManager.reload(id);
return true;
});
// Automatic native context menu for any webContents (windows + webviews)
app.on('web-contents-created', (event, contents) => {
contents.on('context-menu', (e, params) => {