From a6d7bd36048a3cad0eab0107398bc2ae5bd2c730 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Sat, 6 Mar 2021 18:43:45 +0100 Subject: [PATCH] Stop using remote in renderer process Fix unused import --- frontend/ts/index.ts | 393 ++++--------------------- frontend/ts/service-settings.ts | 6 +- frontend/ts/settings.ts | 6 +- src/Window.ts | 14 + src/windows/MainWindow.ts | 421 ++++++++++++++++++++++++--- src/windows/ServiceSettingsWindow.ts | 1 - src/windows/SettingsWindow.ts | 1 - 7 files changed, 468 insertions(+), 374 deletions(-) diff --git a/frontend/ts/index.ts b/frontend/ts/index.ts index efea5cd..e1d507a 100644 --- a/frontend/ts/index.ts +++ b/frontend/ts/index.ts @@ -1,26 +1,14 @@ import { - clipboard, - ContextMenuParams, DidFailLoadEvent, ipcRenderer, PageFaviconUpdatedEvent, - remote, - shell, UpdateTargetUrlEvent, - WebContents, WebviewTag, } from "electron"; import Service from "../../src/Service"; import {IconProperties, IconSet, SpecialPages} from "../../src/Meta"; import Config from "../../src/Config"; -const { - Menu, - MenuItem, - dialog, - session, -} = remote; - const appInfo: { title?: string; } = {}; @@ -42,129 +30,6 @@ let serviceSelector: HTMLElement | null = null; let lastDragPosition: HTMLElement | null = null; let oldActiveService: number | null = null; -// Service context menu -function openServiceContextMenu(event: Event, serviceId: number) { - event.preventDefault(); - const service = services[serviceId]; - if (!service) throw new Error('Service doesn\'t exist.'); - - const menu = new Menu(); - const ready = service.view && service.viewReady, notReady = !service.view && !service.viewReady; - menu.append(new MenuItem({ - label: 'Home', click: () => { - service.view?.loadURL(service.url) - .catch(console.error); - }, - enabled: ready, - })); - menu.append(new MenuItem({ - label: ready ? 'Reload' : 'Load', click: () => { - reloadService(serviceId); - }, - enabled: ready || notReady, - })); - menu.append(new MenuItem({ - label: 'Close', click: () => { - unloadService(serviceId); - }, - enabled: ready, - })); - - menu.append(new MenuItem({type: "separator"})); - - menu.append(new MenuItem({ - label: 'Reset zoom level', click: () => { - if (service.view) { - service.view.setZoomFactor(1); - service.view.setZoomLevel(0); - } - }, - enabled: ready && service.view?.getZoomFactor() !== 1 && service.view?.getZoomLevel() !== 0, - })); - menu.append(new MenuItem({ - label: 'Zoom in', click: () => { - if (service.view) { - service.view.setZoomLevel(service.view.getZoomLevel() + 1); - } - }, - enabled: ready, - })); - menu.append(new MenuItem({ - label: 'Zoom out', click: () => { - if (service.view) { - service.view.setZoomLevel(service.view.getZoomLevel() - 1); - } - }, - enabled: ready, - })); - - menu.append(new MenuItem({type: "separator"})); - - const permissionsMenu = []; - if (ready) { - for (const domain of Object.keys(service.permissions)) { - const domainPermissionsMenu = []; - - const domainPermissions = service.permissions[domain]; - if (domainPermissions) { - for (const permission of domainPermissions) { - domainPermissionsMenu.push({ - label: (permission.authorized ? '✓' : '❌') + ' ' + permission.name, - submenu: [{ - label: 'Toggle', - click: () => { - permission.authorized = !permission.authorized; - updateServicePermissions(serviceId); - }, - }, { - label: 'Forget', - click: () => { - service.permissions[domain] = domainPermissions.filter(p => p !== permission); - }, - }], - }); - } - } - - if (domainPermissionsMenu.length > 0) { - permissionsMenu.push({ - label: domain, - submenu: domainPermissionsMenu, - }); - } - } - } - menu.append(new MenuItem({ - label: 'Permissions', - enabled: ready, - submenu: permissionsMenu, - })); - - menu.append(new MenuItem({type: "separator"})); - - menu.append(new MenuItem({ - label: 'Edit', click: () => { - ipcRenderer.send('openServiceSettings', serviceId); - }, - })); - menu.append(new MenuItem({ - label: 'Delete', click: () => { - dialog.showMessageBox(remote.getCurrentWindow(), { - type: 'question', - title: 'Confirm', - message: 'Are you sure you want to delete this service?', - buttons: ['Cancel', 'Confirm'], - cancelId: 0, - }).then(result => { - if (result.response === 1) { - ipcRenderer.send('deleteService', serviceId); - } - }).catch(console.error); - }, - })); - menu.popup({window: remote.getCurrentWindow()}); -} - ipcRenderer.on('data', ( event, @@ -258,6 +123,44 @@ ipcRenderer.on('data', ( document.documentElement.style.setProperty('--nav-width', config.bigNavBar ? '64px' : '48px'); }); +ipcRenderer.on('load-service-home', (event, serviceId: number) => { + const service = services[serviceId]; + if (!service) throw new Error('Service doesn\'t exist.'); + + service.view?.loadURL(service.url) + .catch(console.error); +}); + +ipcRenderer.on('reload-service', (event, serviceId: number) => { + reloadService(serviceId); +}); + +ipcRenderer.on('unload-service', (event, serviceId: number) => { + unloadService(serviceId); +}); + +ipcRenderer.on('reset-service-zoom-level', (event, serviceId: number) => { + const service = services[serviceId]; + if (service?.view) { + service.view.setZoomFactor(1); + service.view.setZoomLevel(0); + } +}); + +ipcRenderer.on('zoom-in-service', (event, serviceId: number) => { + const service = services[serviceId]; + if (service?.view) { + service.view.setZoomLevel(service.view.getZoomLevel() + 1); + } +}); + +ipcRenderer.on('zoom-out-service', (event, serviceId: number) => { + const service = services[serviceId]; + if (service?.view) { + service.view.setZoomLevel(service.view.getZoomLevel() - 1); + } +}); + function removeServiceFeatures(id: number): Element | null { // Remove nav const nav = document.querySelector('#service-selector'); @@ -358,7 +261,20 @@ function createServiceNavigationElement(index: number, nextNavButton?: Element | ipcRenderer.send('setActiveService', id); } }); - button.addEventListener('contextmenu', e => openServiceContextMenu(e, index)); + button.addEventListener('contextmenu', e => { + e.preventDefault(); + + const service = services[index]; + if (!service) throw new Error('Service doesn\'t exist.'); + + ipcRenderer.send( + 'open-service-navigation-context-menu', + index, + !!service.view && !!service.viewReady, + !service.view && !service.viewReady, + service.view?.getZoomFactor() !== 1 && service.view?.getZoomLevel() !== 0, + ); + }); let icon: HTMLImageElement | HTMLElement; if (service.useFavicon && service.favicon != null) { @@ -490,7 +406,7 @@ document.addEventListener('DOMContentLoaded', () => { refreshButton?.addEventListener('click', () => reload()); addButton = document.getElementById('add-button'); - addButton?.addEventListener('click', () => ipcRenderer.send('openServiceSettings', null)); + addButton?.addEventListener('click', () => ipcRenderer.send('create-new-service', null)); settingsButton = document.getElementById('settings-button'); settingsButton?.addEventListener('click', () => ipcRenderer.send('openSettings', null)); @@ -571,72 +487,17 @@ function loadService(serviceId: number, service: FrontService) { } }); - const webContents = remote.webContents.fromId(view.getWebContentsId()); // Set custom user agent if (typeof service.customUserAgent === 'string') { - webContents.setUserAgent(service.customUserAgent); + ipcRenderer.send('set-web-contents-user-agent', view.getWebContentsId(), service.customUserAgent); } // Set context menu - setContextMenu(webContents); + ipcRenderer.send('open-service-content-context-menu', view.getWebContentsId()); // Set permission request handler - function getUrlDomain(url: string) { - const matches = url.match(/^https?:\/\/((.+?)\/|(.+))/i); - if (matches !== null) { - let domain = matches[1]; - if (domain.endsWith('/')) domain = domain.substr(0, domain.length - 1); - return domain; - } - - return ''; - } - - function getDomainPermissions(domain: string) { - let domainPermissions = service.permissions[domain]; - if (!domainPermissions) domainPermissions = service.permissions[domain] = []; - return domainPermissions; - } - - const serviceSession = session.fromPartition(view.partition); - serviceSession.setPermissionRequestHandler((webContents, permissionName, callback, details) => { - const domain = getUrlDomain(details.requestingUrl); - const domainPermissions = getDomainPermissions(domain); - - const existingPermissions = domainPermissions.filter(p => p.name === permissionName); - if (existingPermissions.length > 0) { - callback(existingPermissions[0].authorized); - return; - } - - dialog.showMessageBox(remote.getCurrentWindow(), { - type: 'question', - title: 'Grant ' + permissionName + ' permission', - message: 'Do you wish to grant the ' + permissionName + ' permission to ' + domain + '?', - buttons: ['Deny', 'Authorize'], - cancelId: 0, - }).then(result => { - const authorized = result.response === 1; - - domainPermissions.push({ - name: permissionName, - authorized: authorized, - }); - updateServicePermissions(serviceId); - - console.log(authorized ? 'Granted' : 'Denied', permissionName, 'for domain', domain); - callback(authorized); - }).catch(console.error); - }); - serviceSession.setPermissionCheckHandler((webContents1, permissionName, requestingOrigin, details) => { - console.log('Permission check', permissionName, requestingOrigin, details); - const domain = getUrlDomain(details.requestingUrl); - const domainPermissions = getDomainPermissions(domain); - - const existingPermissions = domainPermissions.filter(p => p.name === permissionName); - return existingPermissions.length > 0 && existingPermissions[0].authorized; - }); + ipcRenderer.send('set-partition-permissions', serviceId, view.partition); view.setAttribute('src', service.url); }); @@ -717,13 +578,6 @@ function reloadService(serviceId: number) { } } -function updateServicePermissions(serviceId: number) { - const service = services[serviceId]; - if (!service) throw new Error('Service doesn\'t exist.'); - - ipcRenderer.send('updateServicePermissions', serviceId, service.permissions); -} - function updateNavigation() { console.debug('Updating navigation'); // Update active list element @@ -781,11 +635,11 @@ function updateStatusButton() { function updateWindowTitle() { if (selectedServiceId === null) { - ipcRenderer.send('updateWindowTitle', null); + ipcRenderer.send('update-window-title', null); } else { const service = services[selectedServiceId]; if (service?.viewReady && service.view) { - ipcRenderer.send('updateWindowTitle', selectedServiceId, remote.webContents.fromId(service.view.getWebContentsId()).getTitle()); + ipcRenderer.send('update-window-title', selectedServiceId, service.view.getWebContentsId()); } } } @@ -804,14 +658,14 @@ function goForward() { if (selectedServiceId === null) return; const view = services[selectedServiceId]?.view; - if (view) remote.webContents.fromId(view.getWebContentsId()).goForward(); + if (view) ipcRenderer.send('go-forward', view.getWebContentsId()); } function goBack() { if (selectedServiceId === null) return; const view = services[selectedServiceId]?.view; - if (view) remote.webContents.fromId(view.getWebContentsId()).goBack(); + if (view) ipcRenderer.send('go-back', view.getWebContentsId()); } function reload() { @@ -820,133 +674,6 @@ function reload() { reloadService(selectedServiceId); } -function setContextMenu(webContents: WebContents) { - webContents.on('context-menu', (event, props: ContextMenuParams) => { - const menu = new Menu(); - const {editFlags} = props; - - // linkURL - if (props.linkURL.length > 0) { - if (menu.items.length > 0) { - menu.append(new MenuItem({type: 'separator'})); - } - - menu.append(new MenuItem({ - label: 'Copy link URL', - click: () => { - clipboard.writeText(props.linkURL); - }, - })); - menu.append(new MenuItem({ - label: 'Open URL in default browser', - click: () => { - if (props.linkURL.startsWith('https://')) { - shell.openExternal(props.linkURL) - .catch(console.error); - } - }, - })); - } - - // Image - if (props.hasImageContents) { - if (menu.items.length > 0) { - menu.append(new MenuItem({type: 'separator'})); - } - - menu.append(new MenuItem({ - label: 'Copy image', - click: () => { - webContents.copyImageAt(props.x, props.y); - }, - })); - - menu.append(new MenuItem({ - label: 'Save image as', - click: () => { - webContents.downloadURL(props.srcURL); - }, - })); - } - - // Text clipboard - if (editFlags.canUndo || editFlags.canRedo || editFlags.canCut || editFlags.canCopy || editFlags.canPaste || - editFlags.canDelete) { - if (editFlags.canUndo || editFlags.canRedo) { - if (menu.items.length > 0) { - menu.append(new MenuItem({type: 'separator'})); - } - - if (editFlags.canUndo) { - menu.append(new MenuItem({ - label: 'Undo', - role: 'undo', - })); - } - if (editFlags.canRedo) { - menu.append(new MenuItem({ - label: 'Redo', - role: 'redo', - })); - } - } - - if (menu.items.length > 0) { - menu.append(new MenuItem({type: 'separator'})); - } - - menu.append(new MenuItem({ - label: 'Cut', - role: 'cut', - enabled: editFlags.canCut, - })); - menu.append(new MenuItem({ - label: 'Copy', - role: 'copy', - enabled: editFlags.canCopy, - })); - menu.append(new MenuItem({ - label: 'Paste', - role: 'paste', - enabled: editFlags.canPaste, - })); - menu.append(new MenuItem({ - label: 'Delete', - role: 'delete', - enabled: editFlags.canDelete, - })); - } - - if (editFlags.canSelectAll) { - if (menu.items.length > 0) { - menu.append(new MenuItem({type: 'separator'})); - } - - menu.append(new MenuItem({ - label: 'Select all', - role: 'selectAll', - })); - } - - // Inspect element - if (menu.items.length > 0) { - menu.append(new MenuItem({type: 'separator'})); - } - - menu.append(new MenuItem({ - label: 'Inspect element', - click: () => { - webContents.inspectElement(props.x, props.y); - }, - })); - - - menu.popup({ - window: remote.getCurrentWindow(), - }); - }); -} - ipcRenderer.on('fullscreenchange', (e, fullscreen: boolean) => { if (fullscreen) document.body.classList.add('fullscreen'); else document.body.classList.remove('fullscreen'); diff --git a/frontend/ts/service-settings.ts b/frontend/ts/service-settings.ts index ec96e6b..52da27a 100644 --- a/frontend/ts/service-settings.ts +++ b/frontend/ts/service-settings.ts @@ -1,4 +1,4 @@ -import {ipcRenderer, remote} from "electron"; +import {ipcRenderer} from "electron"; import Service from "../../src/Service"; import {IconProperties, IconSet} from "../../src/Meta"; @@ -64,7 +64,7 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('cancel-button')?.addEventListener('click', e => { e.preventDefault(); - remote.getCurrentWindow().close(); + ipcRenderer.send('close-window', 'ServiceSettingsWindow'); }); document.querySelector('form')?.addEventListener('submit', e => { @@ -226,7 +226,7 @@ function save() { } ipcRenderer.send('saveService', serviceId, service); - remote.getCurrentWindow().close(); + ipcRenderer.send('close-window', 'ServiceSettingsWindow'); } function isValid() { diff --git a/frontend/ts/settings.ts b/frontend/ts/settings.ts index 170d7c0..4fc5940 100644 --- a/frontend/ts/settings.ts +++ b/frontend/ts/settings.ts @@ -1,4 +1,4 @@ -import {ipcRenderer, remote, shell} from "electron"; +import {ipcRenderer, shell} from "electron"; import Config from "../../src/Config"; import {SemVer} from "semver"; import {UpdateInfo} from "electron-updater"; @@ -63,7 +63,7 @@ function save() { config.refreshButton = formData.get('refresh-button') === 'on'; ipcRenderer.send('save-config', config); - remote.getCurrentWindow().close(); + ipcRenderer.send('close-window', 'SettingsWindow'); } document.addEventListener('DOMContentLoaded', () => { @@ -87,7 +87,7 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('cancel-button')?.addEventListener('click', e => { e.preventDefault(); - remote.getCurrentWindow().close(); + ipcRenderer.send('close-window', 'SettingsWindow'); }); document.querySelector('form')?.addEventListener('submit', e => { diff --git a/src/Window.ts b/src/Window.ts index 36603f8..4eef618 100644 --- a/src/Window.ts +++ b/src/Window.ts @@ -32,6 +32,19 @@ export default abstract class Window { this.teardown(); this.window = undefined; }); + + this.onIpc('close-window', ( + event, + constructorName: string, + ) => { + if (constructorName === this.constructor.name) { + console.log('Closing', this.constructor.name); + const window = this.getWindow(); + if (window.closable) { + window.close(); + } + } + }); } public teardown(): void { @@ -80,6 +93,7 @@ export default abstract class Window { public getWindow(): BrowserWindow { if (!this.window) throw Error('Window not initialized.'); + else if (this.window.isDestroyed()) throw Error('Window destroyed.'); return this.window; } } diff --git a/src/windows/MainWindow.ts b/src/windows/MainWindow.ts index cb1a275..21fdfc8 100644 --- a/src/windows/MainWindow.ts +++ b/src/windows/MainWindow.ts @@ -1,11 +1,18 @@ import path from "path"; -import {ipcMain} from "electron"; +import { + clipboard, + ContextMenuParams, + dialog, + ipcMain, + Menu, + MenuItem, session, shell, + webContents, +} from "electron"; import ServiceSettingsWindow from "./ServiceSettingsWindow"; import SettingsWindow from "./SettingsWindow"; import Application from "../Application"; import Meta, {SpecialPages} from "../Meta"; import Window from "../Window"; -import {ServicePermissions} from "../Service"; export default class MainWindow extends Window { private activeServiceId: number = 0; @@ -20,7 +27,6 @@ export default class MainWindow extends Window { super.setup({ webPreferences: { nodeIntegration: true, - enableRemoteModule: true, webviewTag: true, contextIsolation: false, }, @@ -82,40 +88,14 @@ export default class MainWindow extends Window { this.config.save(); }); - // Delete service - this.onIpc('deleteService', (e, id: number) => { - console.log('Deleting service', id); - this.config.services.splice(id, 1); - this.config.save(); - - window.webContents.send('deleteService', id); - }); - - // Update service permissions - ipcMain.on('updateServicePermissions', (e, serviceId: number, permissions: ServicePermissions) => { - this.config.services[serviceId].permissions = permissions; - this.config.save(); - }); - // Update window title - ipcMain.on('updateWindowTitle', (event, serviceId: number | null, viewTitle?: string) => { + ipcMain.on('update-window-title', (event, serviceId: number | null, webContentsId?: number) => { if (serviceId === null) { window.setTitle(Meta.title); - } else if (viewTitle) { + } else if (webContentsId) { const service = this.config.services[serviceId]; - window.setTitle(Meta.getTitleForService(service, viewTitle)); - } - }); - - // Open service settings window - ipcMain.on('openServiceSettings', (e, serviceId: number | null) => { - if (!this.serviceSettingsWindow) { - console.log('Opening service settings', serviceId); - this.serviceSettingsWindow = new ServiceSettingsWindow(this.application, this, serviceId); - this.serviceSettingsWindow.setup(); - this.serviceSettingsWindow.onClose(() => { - this.serviceSettingsWindow = undefined; - }); + const serviceWebContents = webContents.fromId(webContentsId); + window.setTitle(Meta.getTitleForService(service, serviceWebContents.getTitle())); } }); @@ -131,6 +111,50 @@ export default class MainWindow extends Window { } }); + // Context menus + ipcMain.on('open-service-navigation-context-menu', ( + event, + serviceId: number, + ready: boolean, + notReady: boolean, + canResetZoom: boolean, + ) => { + this.openServiceNavigationContextMenu(serviceId, ready, notReady, canResetZoom); + }); + ipcMain.on('open-service-content-context-menu', ( + event, + webContentsId: number, + ) => { + this.openServiceContentContextMenu(webContentsId); + }); + + // User agent + ipcMain.on('set-web-contents-user-agent', (event, webContentsId: number, userAgent: string) => { + webContents.fromId(webContentsId).setUserAgent(userAgent); + }); + + // Permission management + ipcMain.on('set-partition-permissions', ( + event, + serviceId: number, + partition: string, + ) => { + this.setPartitionPermissions(serviceId, partition); + }); + + // Navigation + ipcMain.on('go-forward', (event, webContentsId: number) => { + webContents.fromId(webContentsId).goForward(); + }); + ipcMain.on('go-back', (event, webContentsId: number) => { + webContents.fromId(webContentsId).goBack(); + }); + + // Create new service + ipcMain.on('create-new-service', () => { + this.openServiceSettings(null); + }); + window.on('enter-full-screen', () => { window.webContents.send('fullscreenchange', true); }); @@ -161,4 +185,335 @@ export default class MainWindow extends Window { console.log('Set active service', index); this.activeServiceId = index; } + + private openServiceSettings(serviceId: number | null): void { + console.log('o', serviceId, !!this.serviceSettingsWindow); + if (!this.serviceSettingsWindow) { + console.log('Opening service settings', serviceId); + this.serviceSettingsWindow = new ServiceSettingsWindow(this.application, this, serviceId); + this.serviceSettingsWindow.setup(); + this.serviceSettingsWindow.onClose(() => { + this.serviceSettingsWindow = undefined; + }); + } + } + + private deleteService(serviceId: number): void { + console.log('Deleting service', serviceId); + this.config.services.splice(serviceId, 1); + this.config.save(); + + this.getWindow().webContents.send('deleteService', serviceId); + } + + private openServiceNavigationContextMenu( + serviceId: number, + ready: boolean, + notReady: boolean, + canResetZoom: boolean, + ): void { + const ipc = this.getWindow().webContents; + const permissions = this.config.services[serviceId].permissions; + + const menu = new Menu(); + menu.append(new MenuItem({ + label: 'Home', click: () => { + ipc.send('load-service-home', serviceId); + }, + enabled: ready, + })); + menu.append(new MenuItem({ + label: ready ? 'Reload' : 'Load', click: () => { + ipc.send('reload-service', serviceId); + }, + enabled: ready || notReady, + })); + menu.append(new MenuItem({ + label: 'Close', click: () => { + ipc.send('unload-service', serviceId); + }, + enabled: ready, + })); + + menu.append(new MenuItem({type: "separator"})); + + menu.append(new MenuItem({ + label: 'Reset zoom level', click: () => { + ipc.send('reset-service-zoom-level', serviceId); + }, + enabled: ready && canResetZoom, + })); + menu.append(new MenuItem({ + label: 'Zoom in', click: () => { + ipc.send('zoom-in-service', serviceId); + }, + enabled: ready, + })); + menu.append(new MenuItem({ + label: 'Zoom out', click: () => { + ipc.send('zoom-out-service', serviceId); + }, + enabled: ready, + })); + + menu.append(new MenuItem({type: "separator"})); + + const permissionsMenu = []; + if (ready) { + for (const domain of Object.keys(permissions)) { + const domainPermissionsMenu = []; + + const domainPermissions = permissions[domain]; + if (domainPermissions) { + for (const permission of domainPermissions) { + domainPermissionsMenu.push({ + label: (permission.authorized ? '✓' : '❌') + ' ' + permission.name, + submenu: [{ + label: 'Toggle', + click: () => { + permission.authorized = !permission.authorized; + this.config.save(); + }, + }, { + label: 'Forget', + click: () => { + permissions[domain] = domainPermissions.filter(p => p !== permission); + }, + }], + }); + } + } + + if (domainPermissionsMenu.length > 0) { + permissionsMenu.push({ + label: domain, + submenu: domainPermissionsMenu, + }); + } + } + } + menu.append(new MenuItem({ + label: 'Permissions', + enabled: ready, + submenu: permissionsMenu, + })); + + menu.append(new MenuItem({type: "separator"})); + + menu.append(new MenuItem({ + label: 'Edit', click: () => { + this.openServiceSettings(serviceId); + }, + })); + menu.append(new MenuItem({ + label: 'Delete', click: () => { + dialog.showMessageBox(this.getWindow(), { + type: 'question', + title: 'Confirm', + message: 'Are you sure you want to delete this service?', + buttons: ['Cancel', 'Confirm'], + cancelId: 0, + }).then(result => { + if (result.response === 1) { + this.deleteService(serviceId); + } + }).catch(console.error); + }, + })); + menu.popup({window: this.getWindow()}); + } + + private openServiceContentContextMenu( + webContentsId: number, + ): void { + const serviceWebContents = webContents.fromId(webContentsId); + + serviceWebContents.on('context-menu', (event, props: ContextMenuParams) => { + const menu = new Menu(); + const {editFlags} = props; + + // linkURL + if (props.linkURL.length > 0) { + if (menu.items.length > 0) { + menu.append(new MenuItem({type: 'separator'})); + } + + menu.append(new MenuItem({ + label: 'Copy link URL', + click: () => { + clipboard.writeText(props.linkURL); + }, + })); + menu.append(new MenuItem({ + label: 'Open URL in default browser', + click: () => { + if (props.linkURL.startsWith('https://')) { + shell.openExternal(props.linkURL) + .catch(console.error); + } + }, + })); + } + + // Image + if (props.hasImageContents) { + if (menu.items.length > 0) { + menu.append(new MenuItem({type: 'separator'})); + } + + menu.append(new MenuItem({ + label: 'Copy image', + click: () => { + serviceWebContents.copyImageAt(props.x, props.y); + }, + })); + + menu.append(new MenuItem({ + label: 'Save image as', + click: () => { + serviceWebContents.downloadURL(props.srcURL); + }, + })); + } + + // Text clipboard + if (editFlags.canUndo || editFlags.canRedo || editFlags.canCut || editFlags.canCopy || editFlags.canPaste || + editFlags.canDelete) { + if (editFlags.canUndo || editFlags.canRedo) { + if (menu.items.length > 0) { + menu.append(new MenuItem({type: 'separator'})); + } + + if (editFlags.canUndo) { + menu.append(new MenuItem({ + label: 'Undo', + role: 'undo', + })); + } + if (editFlags.canRedo) { + menu.append(new MenuItem({ + label: 'Redo', + role: 'redo', + })); + } + } + + if (menu.items.length > 0) { + menu.append(new MenuItem({type: 'separator'})); + } + + menu.append(new MenuItem({ + label: 'Cut', + role: 'cut', + enabled: editFlags.canCut, + })); + menu.append(new MenuItem({ + label: 'Copy', + role: 'copy', + enabled: editFlags.canCopy, + })); + menu.append(new MenuItem({ + label: 'Paste', + role: 'paste', + enabled: editFlags.canPaste, + })); + menu.append(new MenuItem({ + label: 'Delete', + role: 'delete', + enabled: editFlags.canDelete, + })); + } + + if (editFlags.canSelectAll) { + if (menu.items.length > 0) { + menu.append(new MenuItem({type: 'separator'})); + } + + menu.append(new MenuItem({ + label: 'Select all', + role: 'selectAll', + })); + } + + // Inspect element + if (menu.items.length > 0) { + menu.append(new MenuItem({type: 'separator'})); + } + + menu.append(new MenuItem({ + label: 'Inspect element', + click: () => { + serviceWebContents.inspectElement(props.x, props.y); + }, + })); + + + menu.popup({ + window: this.getWindow(), + }); + }); + } + + private setPartitionPermissions( + serviceId: number, + partition: string, + ): void { + const service = this.config.services[serviceId]; + + function getUrlDomain(url: string) { + const matches = url.match(/^https?:\/\/((.+?)\/|(.+))/i); + if (matches !== null) { + let domain = matches[1]; + if (domain.endsWith('/')) domain = domain.substr(0, domain.length - 1); + return domain; + } + + return ''; + } + + function getDomainPermissions(domain: string) { + let domainPermissions = service.permissions[domain]; + if (!domainPermissions) domainPermissions = service.permissions[domain] = []; + return domainPermissions; + } + + const serviceSession = session.fromPartition(partition); + serviceSession.setPermissionRequestHandler((webContents, permissionName, callback, details) => { + const domain = getUrlDomain(details.requestingUrl); + const domainPermissions = getDomainPermissions(domain); + + const existingPermissions = domainPermissions.filter(p => p.name === permissionName); + if (existingPermissions.length > 0) { + callback(existingPermissions[0].authorized); + return; + } + + dialog.showMessageBox(this.getWindow(), { + type: 'question', + title: 'Grant ' + permissionName + ' permission', + message: 'Do you wish to grant the ' + permissionName + ' permission to ' + domain + '?', + buttons: ['Deny', 'Authorize'], + cancelId: 0, + }).then(result => { + const authorized = result.response === 1; + + domainPermissions.push({ + name: permissionName, + authorized: authorized, + }); + this.config.save(); + + console.log(authorized ? 'Granted' : 'Denied', permissionName, 'for domain', domain); + callback(authorized); + }).catch(console.error); + }); + serviceSession.setPermissionCheckHandler((webContents1, permissionName, requestingOrigin, details) => { + console.log('Permission check', permissionName, requestingOrigin, details); + const domain = getUrlDomain(details.requestingUrl); + const domainPermissions = getDomainPermissions(domain); + + const existingPermissions = domainPermissions.filter(p => p.name === permissionName); + return existingPermissions.length > 0 && existingPermissions[0].authorized; + }); + } } diff --git a/src/windows/ServiceSettingsWindow.ts b/src/windows/ServiceSettingsWindow.ts index 665f723..a026b9c 100644 --- a/src/windows/ServiceSettingsWindow.ts +++ b/src/windows/ServiceSettingsWindow.ts @@ -16,7 +16,6 @@ export default class ServiceSettingsWindow extends Window { super.setup({ webPreferences: { nodeIntegration: true, - enableRemoteModule: true, webviewTag: true, contextIsolation: false, }, diff --git a/src/windows/SettingsWindow.ts b/src/windows/SettingsWindow.ts index a7eabf2..e8b2a54 100644 --- a/src/windows/SettingsWindow.ts +++ b/src/windows/SettingsWindow.ts @@ -10,7 +10,6 @@ export default class SettingsWindow extends Window { super.setup({ webPreferences: { nodeIntegration: true, - enableRemoteModule: true, webviewTag: true, contextIsolation: false, },