1
0
mirror of https://github.com/jiahaog/Nativefier synced 2024-06-27 07:45:03 +02:00
Nativefier/app/src/helpers/windowHelpers.test.ts
Adam Weeden 113d8448c1
Fix CSS injection (#1227)
This fixes https://github.com/nativefier/nativefier/pull/1222#issuecomment-860913698 , where:

1. When it works (e.g. initial page load), CSS is slower to inject (you can see pages without injected CSS)
2. CSS isn't injected when navigating to a page

On both Windows & Linux, a `git revert 9a6c6f870d && npm run build` fixes the issue.

--

I'm still not 100% sure what went wrong, but I suspect that the new version of Electron may not be firing onHeadersReceived for the actual navigation events, and only its child requests. To counteract it, I'm now injecting at the navigation event as well. I was able to reproduce the issue and this does seem to fix it. Please let me know if it does for you as well..

Also I noticed some funkiness in your logs where we're trying to inject on font files. So I realized the method is probably not nearly as important as the content-type, so I've switched blacklist methods to blacklist content-types.
2021-06-15 09:02:57 -04:00

324 lines
9.9 KiB
TypeScript

import {
dialog,
BrowserWindow,
HeadersReceivedResponse,
WebContents,
} from 'electron';
jest.mock('loglevel');
import { error } from 'loglevel';
jest.mock('./helpers');
import { getCSSToInject } from './helpers';
jest.mock('./windowEvents');
import { clearAppData, createNewTab, injectCSS } from './windowHelpers';
describe('clearAppData', () => {
let window: BrowserWindow;
let mockClearCache: jest.SpyInstance;
let mockClearStorageData: jest.SpyInstance;
const mockShowDialog: jest.SpyInstance = jest.spyOn(dialog, 'showMessageBox');
beforeEach(() => {
window = new BrowserWindow();
mockClearCache = jest.spyOn(window.webContents.session, 'clearCache');
mockClearStorageData = jest.spyOn(
window.webContents.session,
'clearStorageData',
);
mockShowDialog.mockReset().mockResolvedValue(undefined);
});
afterAll(() => {
mockClearCache.mockRestore();
mockClearStorageData.mockRestore();
mockShowDialog.mockRestore();
});
test('will not clear app data if dialog canceled', async () => {
mockShowDialog.mockResolvedValue(1);
await clearAppData(window);
expect(mockShowDialog).toHaveBeenCalledTimes(1);
expect(mockClearCache).not.toHaveBeenCalled();
expect(mockClearStorageData).not.toHaveBeenCalled();
});
test('will clear app data if ok is clicked', async () => {
mockShowDialog.mockResolvedValue(0);
await clearAppData(window);
expect(mockShowDialog).toHaveBeenCalledTimes(1);
expect(mockClearCache).not.toHaveBeenCalledTimes(1);
expect(mockClearStorageData).not.toHaveBeenCalledTimes(1);
});
});
describe('createNewTab', () => {
const window = new BrowserWindow();
const options = {};
const setupWindow = jest.fn();
const url = 'https://github.com/nativefier/nativefier';
const mockAddTabbedWindow: jest.SpyInstance = jest.spyOn(
BrowserWindow.prototype,
'addTabbedWindow',
);
const mockFocus: jest.SpyInstance = jest.spyOn(
BrowserWindow.prototype,
'focus',
);
const mockLoadURL: jest.SpyInstance = jest.spyOn(
BrowserWindow.prototype,
'loadURL',
);
test('creates new foreground tab', () => {
const foreground = true;
const tab = createNewTab(options, setupWindow, url, foreground, window);
expect(mockAddTabbedWindow).toHaveBeenCalledWith(tab);
expect(setupWindow).toHaveBeenCalledWith(options, tab);
expect(mockLoadURL).toHaveBeenCalledWith(url);
expect(mockFocus).not.toHaveBeenCalled();
});
test('creates new background tab', () => {
const foreground = false;
const tab = createNewTab(options, setupWindow, url, foreground, window);
expect(mockAddTabbedWindow).toHaveBeenCalledWith(tab);
expect(setupWindow).toHaveBeenCalledWith(options, tab);
expect(mockLoadURL).toHaveBeenCalledWith(url);
expect(mockFocus).toHaveBeenCalledTimes(1);
});
});
describe('injectCSS', () => {
jest.setTimeout(10000);
const mockGetCSSToInject: jest.SpyInstance = getCSSToInject as jest.Mock;
const mockLogError: jest.SpyInstance = error as jest.Mock;
const mockWebContentsInsertCSS: jest.SpyInstance = jest.spyOn(
WebContents.prototype,
'insertCSS',
);
const css = 'body { color: white; }';
let responseHeaders;
beforeEach(() => {
mockGetCSSToInject.mockReset().mockReturnValue('');
mockLogError.mockReset();
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
responseHeaders = { 'x-header': 'value', 'content-type': ['test/other'] };
});
afterAll(() => {
mockGetCSSToInject.mockRestore();
mockLogError.mockRestore();
mockWebContentsInsertCSS.mockRestore();
});
test('will not inject if getCSSToInject is empty', () => {
const window = new BrowserWindow();
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
expect(mockWebContentsInsertCSS).not.toHaveBeenCalled();
});
test('will inject on did-navigate + onHeadersReceived', (done) => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
window.webContents.emit('did-navigate');
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
window.webContents.session.webRequest.send(
'onHeadersReceived',
{ responseHeaders, webContents: window.webContents },
(result: HeadersReceivedResponse) => {
expect(mockWebContentsInsertCSS).toHaveBeenCalledWith(css);
expect(result.cancel).toBe(false);
expect(result.responseHeaders).toBe(responseHeaders);
done();
},
);
});
test('will catch errors inserting CSS', (done) => {
mockGetCSSToInject.mockReturnValue(css);
mockWebContentsInsertCSS.mockReturnValue(
Promise.reject('css insertion error'),
);
const window = new BrowserWindow();
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
window.webContents.emit('did-navigate');
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
window.webContents.session.webRequest.send(
'onHeadersReceived',
{ responseHeaders, webContents: window.webContents },
(result: HeadersReceivedResponse) => {
expect(mockWebContentsInsertCSS).toHaveBeenCalledWith(css);
expect(mockLogError).toHaveBeenCalledWith(
'injectCSSIntoResponse ERROR',
'css insertion error',
);
expect(result.cancel).toBe(false);
expect(result.responseHeaders).toBe(responseHeaders);
done();
},
);
});
test.each<string | jest.DoneCallback>([
'application/json',
'font/woff2',
'image/png',
])(
'will not inject for content-type %s',
(contentType: string, done: jest.DoneCallback) => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
responseHeaders['content-type'] = [contentType];
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
expect(window.webContents.emit('did-navigate')).toBe(true);
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
window.webContents.session.webRequest.send(
'onHeadersReceived',
{
responseHeaders,
webContents: window.webContents,
url: `test-${contentType}`,
},
(result: HeadersReceivedResponse) => {
// insertCSS will still run once for the did-navigate
expect(mockWebContentsInsertCSS).not.toHaveBeenCalled();
expect(result.cancel).toBe(false);
expect(result.responseHeaders).toBe(responseHeaders);
done();
},
);
},
);
test.each<string | jest.DoneCallback>(['text/html'])(
'will inject for content-type %s',
(contentType: string, done: jest.DoneCallback) => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
responseHeaders['content-type'] = [contentType];
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
window.webContents.emit('did-navigate');
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
window.webContents.session.webRequest.send(
'onHeadersReceived',
{
responseHeaders,
webContents: window.webContents,
url: `test-${contentType}`,
},
(result: HeadersReceivedResponse) => {
expect(mockWebContentsInsertCSS).toHaveBeenCalledTimes(1);
expect(result.cancel).toBe(false);
expect(result.responseHeaders).toBe(responseHeaders);
done();
},
);
},
);
test.each<string | jest.DoneCallback>([
'image',
'script',
'stylesheet',
'xhr',
])(
'will not inject for resource type %s',
(resourceType: string, done: jest.DoneCallback) => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
window.webContents.emit('did-navigate');
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
window.webContents.session.webRequest.send(
'onHeadersReceived',
{
responseHeaders,
webContents: window.webContents,
resourceType,
url: `test-${resourceType}`,
},
(result: HeadersReceivedResponse) => {
// insertCSS will still run once for the did-navigate
expect(mockWebContentsInsertCSS).not.toHaveBeenCalled();
expect(result.cancel).toBe(false);
expect(result.responseHeaders).toBe(responseHeaders);
done();
},
);
},
);
test.each<string | jest.DoneCallback>(['html', 'other'])(
'will inject for resource type %s',
(resourceType: string, done: jest.DoneCallback) => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
window.webContents.emit('did-navigate');
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
window.webContents.session.webRequest.send(
'onHeadersReceived',
{
responseHeaders,
webContents: window.webContents,
resourceType,
url: `test-${resourceType}`,
},
(result: HeadersReceivedResponse) => {
expect(mockWebContentsInsertCSS).toHaveBeenCalledTimes(1);
expect(result.cancel).toBe(false);
expect(result.responseHeaders).toBe(responseHeaders);
done();
},
);
},
);
});