Update to Electron 25 (#1559)

This is intended to get Electron updated to 25. There are no known bugs
in this release.

As well this includes a fix for an existing bug I noticed where child
windows in Windows received a menu bar when they should not have.
This commit is contained in:
Adam Weeden 2023-08-25 09:10:05 -04:00 committed by GitHub
parent be418d4349
commit 64157c3c5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2240 additions and 8170 deletions

View File

@ -8,7 +8,7 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1 # Setup .npmrc file to publish to npm
- uses: actions/setup-node@v2 # Setup .npmrc file to publish to npm
with:
node-version: '20' # Align the version of Node here with ci.yml.
registry-url: 'https://registry.npmjs.org'
@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1 # Setup .npmrc file to publish to npm
- uses: actions/setup-node@v2 # Setup .npmrc file to publish to npm
with:
node-version: '20' # Align the version of Node here with ci.yml.
registry-url: 'https://registry.npmjs.org'

1
.gitignore vendored
View File

@ -44,7 +44,6 @@ build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
.nvmrc
# Python virtual environment in case it's created for the Castlabs code signing tool
venv

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
16

1333
app/npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,14 +12,14 @@
],
"scripts": {},
"dependencies": {
"electron-context-menu": "^3.1.1",
"electron-dl": "^3.2.1",
"electron-context-menu": "^3.6.1",
"electron-dl": "^3.5.0",
"electron-squirrel-startup": "^1.0.0",
"electron-window-state": "^5.0.3",
"loglevel": "^1.7.1",
"source-map-support": "^0.5.19"
"loglevel": "^1.8.1",
"source-map-support": "^0.5.21"
},
"devDependencies": {
"electron": "^21.4.4"
"electron": "^25.5.0"
}
}

View File

@ -1,7 +1,7 @@
import {
BrowserWindow,
ContextMenuParams,
NewWindowWebContentsEvent,
Event as ElectronEvent,
} from 'electron';
import contextMenu from 'electron-context-menu';
@ -25,7 +25,7 @@ export function initContextMenu(
prepend: (actions: contextMenu.Actions, params: ContextMenuParams) => {
log.debug('contextMenu.prepend', { actions, params });
const items = [];
if (params.linkURL) {
if (params.linkURL && window) {
items.push({
label: 'Open Link in Default Browser',
click: () => {
@ -38,36 +38,40 @@ export function initContextMenu(
label: 'Open Link in New Window',
click: () =>
createNewWindow(
outputOptionsToWindowOptions(options),
outputOptionsToWindowOptions(options, nativeTabsSupported()),
setupNativefierWindow,
params.linkURL,
window,
// window,
),
});
if (nativeTabsSupported()) {
items.push({
label: 'Open Link in New Tab',
click: () =>
// Fire a new window event for a foreground tab
// Previously we called createNewTab directly, but it had incosistent and buggy behavior
// as it was mostly designed for running off of events. So this will create a new event
// for a foreground-tab for the event handler to grab and take care of instead.
(window as BrowserWindow).webContents.emit(
// event name
'new-window',
// event object
{
// Leave to the default for a NewWindowWebContentsEvent
newGuest: undefined,
...new Event('new-window'),
} as NewWindowWebContentsEvent,
// url
params.linkURL,
// frameName
window?.webContents.mainFrame.name ?? '',
// disposition
'foreground-tab',
),
// // Fire a new window event for a foreground tab
// // Previously we called createNewTab directly, but it had incosistent and buggy behavior
// // as it was mostly designed for running off of events. So this will create a new event
// // for a foreground-tab for the event handler to grab and take care of instead.
// (window as BrowserWindow).webContents.emit(
// // event name
// 'new-window',
// // event object
// {
// // Leave to the default for a NewWindowWebContentsEvent
// newGuest: undefined,
// ...new Event('new-window'),
// }, // as NewWindowWebContentsEvent,
// // url
// params.linkURL,
// // frameName
// window?.webContents.mainFrame.name ?? '',
// // disposition
// 'foreground-tab',
// ),
window.emit('new-window-for-tab', {
...new Event('new-window-for-tab'),
url: params.linkURL,
} as ElectronEvent<{ url: string }>),
});
}
}

View File

@ -3,15 +3,19 @@ import * as path from 'path';
import { BrowserWindow, ipcMain } from 'electron';
import * as log from '../helpers/loggingHelper';
import { nativeTabsSupported } from '../helpers/helpers';
export async function createLoginWindow(
loginCallback: (username?: string, password?: string) => void,
parent?: BrowserWindow,
): Promise<BrowserWindow> {
log.debug('createLoginWindow', { loginCallback, parent });
log.debug('createLoginWindow', {
loginCallback,
parent,
});
const loginWindow = new BrowserWindow({
parent,
parent: nativeTabsSupported() ? undefined : parent,
width: 300,
height: 400,
frame: false,

View File

@ -1,7 +1,13 @@
import * as fs from 'fs';
import * as path from 'path';
import { ipcMain, desktopCapturer, BrowserWindow, Event } from 'electron';
import {
desktopCapturer,
ipcMain,
BrowserWindow,
Event,
HandlerDetails,
} from 'electron';
import windowStateKeeper from 'electron-window-state';
import { initContextMenu } from './contextMenu';
@ -36,9 +42,9 @@ type SessionInteractionRequest = {
propertyValue?: unknown;
};
type SessionInteractionResult = {
type SessionInteractionResult<T = unknown> = {
id?: string;
value?: unknown | Promise<unknown>;
value?: T | Promise<T>;
error?: Error;
};
@ -78,7 +84,9 @@ export async function createMainWindow(
// So, we manually mainWindow.show() later, see a few lines below
show: options.tray !== 'start-in-tray' && process.platform !== 'win32',
backgroundColor: options.backgroundColor,
...getDefaultWindowOptions(outputOptionsToWindowOptions(options)),
...getDefaultWindowOptions(
outputOptionsToWindowOptions(options, nativeTabsSupported()),
),
});
// Just load about:blank to start, gives playwright something to latch onto initially for testing.
@ -102,44 +110,32 @@ export async function createMainWindow(
mainWindow.show();
}
const windowOptions = outputOptionsToWindowOptions(options);
const windowOptions = outputOptionsToWindowOptions(
options,
nativeTabsSupported(),
);
createMenu(options, mainWindow);
createContextMenu(options, mainWindow);
setupNativefierWindow(windowOptions, mainWindow);
// .on('new-window', ...) is deprected in favor of setWindowOpenHandler(...)
// We can't quite cut over to that yet for a few reasons:
// 1. Our version of Electron does not yet support a parameter to
// setWindowOpenHandler that contains `disposition', which we need.
// See https://github.com/electron/electron/issues/28380
// 2. setWindowOpenHandler doesn't support newGuest as well
// Though at this point, 'new-window' bugs seem to be coming up and downstream
// users are being pointed to use setWindowOpenHandler.
// E.g., https://github.com/electron/electron/issues/28374
// Note it is important to add these handlers only to the *main* window,
// else we run into weird behavior like opening tabs twice
mainWindow.webContents.on(
'new-window',
(event, url, frameName, disposition) => {
onNewWindow(
windowOptions,
setupNativefierWindow,
event,
url,
frameName,
disposition,
).catch((err) => log.error('onNewWindow ERROR', err));
},
);
// @ts-expect-error new-tab isn't in the type definition, but it does exist
mainWindow.on('new-tab', () => {
mainWindow.webContents.setWindowOpenHandler((details: HandlerDetails) => {
return onNewWindow(
windowOptions,
setupNativefierWindow,
details,
mainWindow,
);
});
mainWindow.on('new-window-for-tab', (event?: Event<{ url?: string }>) => {
log.debug('mainWindow.new-window-for-tab', { event });
createNewTab(
windowOptions,
setupNativefierWindow,
options.targetUrl,
event?.url ?? options.targetUrl,
true,
mainWindow,
// mainWindow,
);
});
@ -154,7 +150,7 @@ export async function createMainWindow(
mainWindow.show();
});
setupSessionInteraction(options, mainWindow);
setupSessionInteraction(mainWindow);
setupSessionPermissionHandler(mainWindow);
if (options.clearCache) {
@ -265,10 +261,7 @@ function setupNotificationBadge(
});
}
function setupSessionInteraction(
options: OutputOptions,
window: BrowserWindow,
): void {
function setupSessionInteraction(window: BrowserWindow): void {
// See API.md / "Accessing The Electron Session"
ipcMain.on(
'session-interaction',

View File

@ -2,7 +2,7 @@ jest.mock('./helpers');
jest.mock('./windowEvents');
jest.mock('./windowHelpers');
import { dialog, BrowserWindow } from 'electron';
import { dialog, BrowserWindow, HandlerDetails, WebContents } from 'electron';
import { WindowOptions } from '../../../shared/src/options/model';
import { linkIsInternal, openExternal, nativeTabsSupported } from './helpers';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@ -14,11 +14,9 @@ const {
onNewWindowHelper: (
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
urlToGo: string,
disposition: string | undefined,
preventDefault: (newGuest: BrowserWindow) => void,
details: Partial<HandlerDetails>,
parent?: BrowserWindow,
) => Promise<void>;
) => ReturnType<Parameters<WebContents['setWindowOpenHandler']>[0]>;
onWillNavigate: (
options: {
blockExternalUrls: boolean;
@ -43,12 +41,13 @@ describe('onNewWindowHelper', () => {
const foregroundDisposition = 'foreground-tab';
const backgroundDisposition = 'background-tab';
const baseOptions = {
autoHideMenuBar: true,
blockExternalUrls: false,
insecure: false,
name: 'TEST_APP',
targetUrl: originalURL,
zoom: 1.0,
};
} as WindowOptions;
const mockShowNavigationBlockedMessage: jest.SpyInstance =
showNavigationBlockedMessage as jest.Mock;
const mockCreateAboutBlank: jest.SpyInstance =
@ -60,7 +59,6 @@ describe('onNewWindowHelper', () => {
const mockNativeTabsSupported: jest.SpyInstance =
nativeTabsSupported as jest.Mock;
const mockOpenExternal: jest.SpyInstance = openExternal as jest.Mock;
const preventDefault = jest.fn();
const setupWindow = jest.fn();
beforeEach(() => {
@ -72,7 +70,6 @@ describe('onNewWindowHelper', () => {
mockLinkIsInternal.mockReset().mockReturnValue(true);
mockNativeTabsSupported.mockReset().mockReturnValue(false);
mockOpenExternal.mockReset();
preventDefault.mockReset();
setupWindow.mockReset();
});
@ -85,105 +82,84 @@ describe('onNewWindowHelper', () => {
mockOpenExternal.mockRestore();
});
test('internal urls should not be handled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
internalURL,
undefined,
preventDefault,
);
test('internal urls should not be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).not.toHaveBeenCalled();
expect(result.action).toEqual('allow');
});
test('external urls should be opened externally', async () => {
test('external urls should be opened externally', () => {
mockLinkIsInternal.mockReturnValue(false);
await onNewWindowHelper(
baseOptions,
setupWindow,
externalURL,
undefined,
preventDefault,
);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: externalURL,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).toHaveBeenCalledTimes(1);
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
});
test('external urls should be ignored if blockExternalUrls is true', async () => {
test('external urls should be ignored if blockExternalUrls is true', () => {
mockLinkIsInternal.mockReturnValue(false);
const options = {
...baseOptions,
blockExternalUrls: true,
};
await onNewWindowHelper(
options,
setupWindow,
externalURL,
undefined,
preventDefault,
);
const result = onNewWindowHelper(options, setupWindow, {
url: externalURL,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).toHaveBeenCalledTimes(1);
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
});
test('tab disposition should be ignored if tabs are not enabled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
internalURL,
foregroundDisposition,
preventDefault,
);
test('tab disposition should be ignored if tabs are not enabled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
disposition: foregroundDisposition,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).not.toHaveBeenCalled();
expect(result.action).toEqual('allow');
});
test('tab disposition should be ignored if url is external', async () => {
test('tab disposition should be ignored if url is external', () => {
mockLinkIsInternal.mockReturnValue(false);
await onNewWindowHelper(
baseOptions,
setupWindow,
externalURL,
foregroundDisposition,
preventDefault,
);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: externalURL,
disposition: foregroundDisposition,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).toHaveBeenCalledTimes(1);
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
});
test('foreground tabs with internal urls should be opened in the foreground', async () => {
test('foreground tabs with internal urls should be opened in the foreground', () => {
mockNativeTabsSupported.mockReturnValue(true);
await onNewWindowHelper(
baseOptions,
setupWindow,
internalURL,
foregroundDisposition,
preventDefault,
);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
disposition: foregroundDisposition,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).toHaveBeenCalledTimes(1);
@ -192,23 +168,19 @@ describe('onNewWindowHelper', () => {
setupWindow,
internalURL,
true,
undefined,
);
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
});
test('background tabs with internal urls should be opened in background tabs', async () => {
test('background tabs with internal urls should be opened in background tabs', () => {
mockNativeTabsSupported.mockReturnValue(true);
await onNewWindowHelper(
baseOptions,
setupWindow,
internalURL,
backgroundDisposition,
preventDefault,
);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
disposition: backgroundDisposition,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).toHaveBeenCalledTimes(1);
@ -217,59 +189,46 @@ describe('onNewWindowHelper', () => {
setupWindow,
internalURL,
false,
undefined,
);
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
});
test('about:blank urls should be handled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
'about:blank',
undefined,
preventDefault,
);
test('about:blank urls should be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: 'about:blank',
});
expect(mockCreateAboutBlank).toHaveBeenCalledTimes(1);
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
});
test('about:blank#blocked urls should be handled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
'about:blank#blocked',
undefined,
preventDefault,
);
test('about:blank#blocked urls should be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: 'about:blank#blocked',
});
expect(mockCreateAboutBlank).toHaveBeenCalledTimes(1);
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
});
test('about:blank#other urls should not be handled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
'about:blank#other',
undefined,
preventDefault,
);
test('about:blank#other urls should not be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: 'about:blank#other',
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).not.toHaveBeenCalled();
expect(result.action).toEqual('allow');
});
});

View File

@ -2,8 +2,8 @@ import {
dialog,
BrowserWindow,
Event,
NewWindowWebContentsEvent,
WebContents,
HandlerDetails,
} from 'electron';
import { linkIsInternal, nativeTabsSupported, openExternal } from './helpers';
@ -18,84 +18,64 @@ import {
} from './windowHelpers';
import { WindowOptions } from '../../../shared/src/options/model';
type NewWindowHandlerResult = ReturnType<
Parameters<WebContents['setWindowOpenHandler']>[0]
>;
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',
details: HandlerDetails,
parent?: BrowserWindow,
): Promise<void> {
): NewWindowHandlerResult {
log.debug('onNewWindow', {
event,
urlToGo,
frameName,
disposition,
parent,
details,
});
const preventDefault = (newGuest?: BrowserWindow): void => {
log.debug('onNewWindow.preventDefault', { newGuest, event });
if (event.preventDefault) {
event.preventDefault();
}
if (newGuest) {
event.newGuest = newGuest;
}
};
return onNewWindowHelper(
options,
setupWindow,
urlToGo,
disposition,
preventDefault,
parent,
details,
nativeTabsSupported() ? undefined : parent,
);
}
export function onNewWindowHelper(
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
urlToGo: string,
disposition: string | undefined,
preventDefault: (newGuest?: BrowserWindow) => void,
details: HandlerDetails,
parent?: BrowserWindow,
): Promise<void> {
): NewWindowHandlerResult {
log.debug('onNewWindowHelper', {
options,
urlToGo,
disposition,
preventDefault,
parent,
details,
});
try {
if (
!linkIsInternal(
options.targetUrl,
urlToGo,
details.url,
options.internalUrls,
options.strictInternalUrls,
)
) {
preventDefault();
if (options.blockExternalUrls) {
return new Promise((resolve) => {
showNavigationBlockedMessage(
`Navigation to external URL blocked by options: ${urlToGo}`,
)
.then(() => resolve())
.catch((err: unknown) => {
throw err;
});
});
showNavigationBlockedMessage(
`Navigation to external URL blocked by options: ${details.url}`,
)
.then(() => {
// blockExternalURL(details.url).then(resolve).catch((err: unknown) => {
// log.error('blockExternalURL', err);
// });
})
.catch((err: unknown) => {
throw err;
});
return { action: 'deny' };
} else {
return openExternal(urlToGo);
openExternal(details.url).catch((err: unknown) => {
log.error('openExternal', err);
});
return { action: 'deny' };
}
}
// Normally the following would be:
@ -103,26 +83,25 @@ export function onNewWindowHelper(
// 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 (['about:blank', 'about:blank#blocked'].includes(details.url)) {
createAboutBlankWindow(
options,
setupWindow,
nativeTabsSupported() ? undefined : parent,
);
return { action: 'deny' };
} else if (nativeTabsSupported()) {
return Promise.resolve(
preventDefault(
createNewTab(
options,
setupWindow,
urlToGo,
disposition === 'foreground-tab',
parent,
),
),
createNewTab(
options,
setupWindow,
details.url,
details.disposition === 'foreground-tab',
);
return { action: 'deny' };
}
return Promise.resolve(undefined);
return { action: 'allow' };
} catch (err: unknown) {
return Promise.reject(err);
return { action: 'deny' };
}
}

View File

@ -52,14 +52,15 @@ describe('clearAppData', () => {
});
describe('createNewTab', () => {
const window = new BrowserWindow();
// const window = new BrowserWindow();
const options: WindowOptions = {
autoHideMenuBar: true,
blockExternalUrls: false,
insecure: false,
name: 'Test App',
targetUrl: 'https://github.com/nativefier/natifefier',
zoom: 1.0,
};
} as WindowOptions;
const setupWindow = jest.fn();
const url = 'https://github.com/nativefier/nativefier';
const mockAddTabbedWindow: jest.SpyInstance = jest.spyOn(
@ -78,7 +79,7 @@ describe('createNewTab', () => {
test('creates new foreground tab', () => {
const foreground = true;
const tab = createNewTab(options, setupWindow, url, foreground, window);
const tab = createNewTab(options, setupWindow, url, foreground);
expect(mockAddTabbedWindow).toHaveBeenCalledWith(tab);
expect(setupWindow).toHaveBeenCalledWith(options, tab);
@ -89,7 +90,13 @@ describe('createNewTab', () => {
test('creates new background tab', () => {
const foreground = false;
const tab = createNewTab(options, setupWindow, url, foreground, window);
const tab = createNewTab(
options,
setupWindow,
url,
foreground,
// window
);
expect(mockAddTabbedWindow).toHaveBeenCalledWith(tab);
expect(setupWindow).toHaveBeenCalledWith(options, tab);

View File

@ -13,6 +13,7 @@ import {
import { getCSSToInject, isOSX, nativeTabsSupported } from './helpers';
import * as log from './loggingHelper';
import { TrayValue, WindowOptions } from '../../../shared/src/options/model';
import { randomUUID } from 'crypto';
const ZOOM_INTERVAL = 0.1;
@ -73,7 +74,7 @@ export function createAboutBlankWindow(
{ ...options, show: false },
setupWindow,
'about:blank',
parent,
nativeTabsSupported() ? undefined : parent,
);
window.webContents.once('did-stop-loading', () => {
if (window.webContents.getURL() === 'about:blank') {
@ -90,11 +91,16 @@ export function createNewTab(
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
url: string,
foreground: boolean,
parent?: BrowserWindow,
): BrowserWindow | undefined {
log.debug('createNewTab', { url, foreground, parent });
const focusedWindow = BrowserWindow.getFocusedWindow();
log.debug('createNewTab', {
url,
foreground,
focusedWindow,
});
return withFocusedWindow((focusedWindow) => {
const newTab = createNewWindow(options, setupWindow, url, parent);
const newTab = createNewWindow(options, setupWindow, url);
log.debug('createNewTab.withFocusedWindow', { focusedWindow, newTab });
focusedWindow.addTabbedWindow(newTab);
if (!foreground) {
focusedWindow.focus();
@ -109,9 +115,12 @@ export function createNewWindow(
url: string,
parent?: BrowserWindow,
): BrowserWindow {
log.debug('createNewWindow', { url, parent });
const window = new BrowserWindow({
log.debug('createNewWindow', {
url,
parent,
});
const window = new BrowserWindow({
parent: nativeTabsSupported() ? undefined : parent,
...getDefaultWindowOptions(options),
});
setupWindow(options, window);
@ -141,8 +150,11 @@ export function getDefaultWindowOptions(
};
const defaultOptions: BrowserWindowConstructorOptions = {
autoHideMenuBar: options.autoHideMenuBar,
fullscreenable: true,
tabbingIdentifier: nativeTabsSupported() ? options.name : undefined,
tabbingIdentifier: nativeTabsSupported()
? options.tabbingIdentifier ?? randomUUID()
: undefined,
title: options.name,
webPreferences: {
javascript: true,

View File

@ -202,7 +202,8 @@ const setDockBadge = isOSX()
? (count?: number | string, bounce = false): void => {
if (count !== undefined) {
app.dock.setBadge(count.toString());
if (bounce && count > currentBadgeCount) app.dock.bounce();
if (bounce && typeof count === 'number' && count > currentBadgeCount)
app.dock.bounce();
currentBadgeCount = typeof count === 'number' ? count : 0;
}
}
@ -309,10 +310,10 @@ if (shouldQuit) {
});
}
app.on('new-window-for-tab', () => {
log.debug('app.new-window-for-tab');
app.on('new-window-for-tab', (event: Event) => {
log.debug('app.new-window-for-tab', { event });
if (mainWindow) {
mainWindow.emit('new-tab');
mainWindow.emit('new-window-for-tab', event);
}
});
@ -332,9 +333,10 @@ app.on(
if (appArgs.basicAuthUsername && appArgs.basicAuthPassword) {
callback(appArgs.basicAuthUsername, appArgs.basicAuthPassword);
} else {
createLoginWindow(callback, mainWindow).catch((err) =>
log.error('createLoginWindow ERROR', err),
);
createLoginWindow(
callback,
// mainWindow
).catch((err) => log.error('createLoginWindow ERROR', err));
}
},
);

View File

@ -21,7 +21,6 @@ module.exports = {
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-implicit-any-catch': 'error',
'@typescript-eslint/no-invalid-void-type': 'error',
'@typescript-eslint/prefer-ts-expect-error': 'error',
'@typescript-eslint/type-annotation-spacing': 'error',

8531
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -58,43 +58,44 @@
"watch": "npx concurrently \"npm:*:watch\""
},
"dependencies": {
"axios": "^1.1.3",
"electron-packager": "^15.5.1",
"fs-extra": "^10.0.0",
"gitcloud": "^0.2.3",
"@electron/asar": "^3.2.4",
"axios": "^1.4.0",
"electron-packager": "^17.1.1",
"fs-extra": "^11.1.1",
"gitcloud": "^0.2.4",
"hasbin": "^1.2.3",
"loglevel": "^1.7.1",
"loglevel": "^1.8.1",
"ncp": "^2.0.0",
"page-icon": "^0.4.0",
"sanitize-filename": "^1.6.3",
"source-map-support": "^0.5.19",
"source-map-support": "^0.5.21",
"tmp": "^0.2.1",
"yargs": "^17.1.1"
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/debug": "^4.1.6",
"@types/fs-extra": "^9.0.13",
"@types/debug": "^4.1.8",
"@types/fs-extra": "^11.0.1",
"@types/hasbin": "^1.2.0",
"@types/jest": "^28.1.6",
"@types/jest": "^29.5.3",
"@types/ncp": "^2.0.5",
"@types/node": "^16.0.0",
"@types/node": "^20.4.7",
"@types/page-icon": "^0.3.4",
"@types/tmp": "^0.2.1",
"@types/yargs": "^17.0.10",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"electron": "^21.4.4",
"eslint": "^8.1.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^28.1.3",
"playwright": "^1.24.0",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"ts-loader": "^9.2.3",
"typescript": "^4.3.5",
"webpack": "^5.45.1",
"webpack-cli": "^4.7.2"
"@types/tmp": "^0.2.3",
"@types/yargs": "^17.0.24",
"@typescript-eslint/eslint-plugin": "^6.2.1",
"@typescript-eslint/parser": "^6.2.1",
"electron": "^25.5.0",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.6.2",
"playwright": "^1.36.2",
"prettier": "^3.0.1",
"rimraf": "^5.0.1",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
},
"jest_COMMENTS": {
"testPathIgnorePatterns": "See https://jestjs.io/docs/configuration#testpathignorepatterns-arraystring . We set it to 1. ignore coverage for deps, and 2. be sure we test the compiled JS, which is in `lib`, not `src` or `dist`",

View File

@ -1,4 +1,5 @@
import { CreateOptions } from 'asar';
import { CreateOptions } from '@electron/asar';
import { randomUUID } from 'crypto';
import * as electronPackager from 'electron-packager';
export type TitleBarValue =
@ -117,6 +118,7 @@ export type OutputOptions = NativefierOptions & {
nativefierVersion: string;
oldBuildWarningText: string;
strictInternalUrls: boolean;
tabbingIdentifier?: string;
targetUrl: string;
userAgent?: string;
zoom?: number;
@ -205,6 +207,7 @@ export type RawOptions = {
};
export type WindowOptions = {
autoHideMenuBar: boolean;
blockExternalUrls: boolean;
browserwindowOptions?: BrowserWindowOptions;
insecure: boolean;
@ -213,6 +216,7 @@ export type WindowOptions = {
name: string;
proxyRules?: string;
show?: boolean;
tabbingIdentifier?: string;
targetUrl: string;
userAgent?: string;
zoom: number;
@ -220,10 +224,15 @@ export type WindowOptions = {
export function outputOptionsToWindowOptions(
options: OutputOptions,
generateTabbingIdentifierIfMissing: boolean,
): WindowOptions {
return {
...options,
autoHideMenuBar: !options.showMenuBar,
insecure: options.insecure ?? false,
tabbingIdentifier: generateTabbingIdentifierIfMissing
? options.tabbingIdentifier ?? randomUUID()
: options.tabbingIdentifier,
zoom: options.zoom ?? 1.0,
};
}

View File

@ -6,19 +6,19 @@ export const DEFAULT_APP_NAME = 'APP';
// - upgrade app / package.json / "devDependencies" / "electron"
// - upgrade package.json / "devDependencies" / "electron"
// Doing a *major* upgrade? Read https://github.com/nativefier/nativefier/blob/master/HACKING.md#deps-major-upgrading-electron
export const DEFAULT_ELECTRON_VERSION = '21.4.4';
export const DEFAULT_ELECTRON_VERSION = '25.5.0';
// https://atom.io/download/atom-shell/index.json
// https://www.electronjs.org/releases/stable
export const DEFAULT_CHROME_VERSION = '106.0.5249.199';
export const DEFAULT_CHROME_VERSION = '114.0.5735.289';
// Update each of these periodically
// https://product-details.mozilla.org/1.0/firefox_versions.json
export const DEFAULT_FIREFOX_VERSION = '116.0';
export const DEFAULT_FIREFOX_VERSION = '116.0.1';
// https://en.wikipedia.org/wiki/Safari_version_history
export const DEFAULT_SAFARI_VERSION = {
majorVersion: 65,
version: '16.5.2',
majorVersion: 16,
version: '16.6',
webkitVersion: '605.1.15',
};

View File

@ -6,6 +6,7 @@
"incremental": true,
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,