import { dialog, BrowserWindow, Event, NewWindowWebContentsEvent, WebContents, } from 'electron'; import log from 'loglevel'; import { WindowOptions } from '../../../shared/src/options/model'; import { linkIsInternal, nativeTabsSupported, openExternal } from './helpers'; import { blockExternalURL, createAboutBlankWindow, createNewTab, injectCSS, sendParamsOnDidFinishLoad, setProxyRules, } from './windowHelpers'; export function onNewWindow( options: WindowOptions, setupWindow: (options: WindowOptions, window: BrowserWindow) => void, event: NewWindowWebContentsEvent, urlToGo: string, frameName: string, disposition: | 'default' | 'foreground-tab' | 'background-tab' | 'new-window' | 'save-to-disk' | 'other', parent?: BrowserWindow, ): Promise { log.debug('onNewWindow', { event, urlToGo, frameName, disposition, parent, }); const preventDefault = (newGuest?: BrowserWindow): void => { log.debug('onNewWindow.preventDefault', { newGuest, event }); event.preventDefault(); if (newGuest) { event.newGuest = newGuest; } }; return onNewWindowHelper( options, setupWindow, urlToGo, disposition, preventDefault, parent, ); } export function onNewWindowHelper( options: WindowOptions, setupWindow: (options: WindowOptions, window: BrowserWindow) => void, urlToGo: string, disposition: string | undefined, preventDefault: (newGuest?: BrowserWindow) => void, parent?: BrowserWindow, ): Promise { log.debug('onNewWindowHelper', { options, urlToGo, disposition, preventDefault, parent, }); try { if (!linkIsInternal(options.targetUrl, urlToGo, options.internalUrls)) { preventDefault(); if (options.blockExternalUrls) { return new Promise((resolve) => { blockExternalURL(urlToGo) .then(() => resolve()) .catch((err: unknown) => { throw err; }); }); } else { return openExternal(urlToGo); } } // Normally the following would be: // if (urlToGo.startsWith('about:blank'))... // But due to a bug we resolved in https://github.com/nativefier/nativefier/issues/1197 // Some sites use about:blank#something to use as placeholder windows to fill // with content via JavaScript. So we'll stay specific for now... else if (['about:blank', 'about:blank#blocked'].includes(urlToGo)) { return Promise.resolve( preventDefault(createAboutBlankWindow(options, setupWindow, parent)), ); } else if (nativeTabsSupported()) { return Promise.resolve( preventDefault( createNewTab( options, setupWindow, urlToGo, disposition === 'foreground-tab', parent, ), ), ); } return Promise.resolve(undefined); } catch (err: unknown) { return Promise.reject(err); } } export function onWillNavigate( options: WindowOptions, event: Event, urlToGo: string, ): Promise { log.debug('onWillNavigate', { options, event, urlToGo }); if (!linkIsInternal(options.targetUrl, urlToGo, options.internalUrls)) { event.preventDefault(); if (options.blockExternalUrls) { return new Promise((resolve) => { blockExternalURL(urlToGo) .then(() => resolve()) .catch((err: unknown) => { throw err; }); }); } else { return openExternal(urlToGo); } } return Promise.resolve(undefined); } export function onWillPreventUnload( event: Event & { sender?: WebContents }, ): void { log.debug('onWillPreventUnload', event); const webContents = event.sender; if (!webContents) { return; } const browserWindow = BrowserWindow.fromWebContents(webContents) ?? BrowserWindow.getFocusedWindow(); if (browserWindow) { const choice = dialog.showMessageBoxSync(browserWindow, { type: 'question', buttons: ['Proceed', 'Stay'], message: 'You may have unsaved changes, are you sure you want to proceed?', title: 'Changes you made may not be saved.', defaultId: 0, cancelId: 1, }); if (choice === 0) { event.preventDefault(); } } } export function setupNativefierWindow( options: WindowOptions, window: BrowserWindow, ): void { if (options.userAgent) { window.webContents.userAgent = options.userAgent; } if (options.proxyRules) { setProxyRules(window, options.proxyRules); } injectCSS(window); window.webContents.on('will-navigate', (event: Event, url: string) => { onWillNavigate(options, event, url).catch((err) => { log.error('window.webContents.on.will-navigate ERROR', err); event.preventDefault(); }); }); window.webContents.on('will-prevent-unload', onWillPreventUnload); sendParamsOnDidFinishLoad(options, window); // @ts-expect-error new-tab isn't in the type definition, but it does exist window.on('new-tab', () => { createNewTab( options, setupNativefierWindow, options.targetUrl, true, window, ); }); }