electron/spec/api-subframe-spec.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

289 lines
11 KiB
TypeScript
Raw Normal View History

2019-08-28 20:55:01 +00:00
import { expect } from 'chai';
import * as path from 'node:path';
import * as http from 'node:http';
import { emittedNTimes } from './lib/events-helpers';
import { closeWindow } from './lib/window-helpers';
import { app, BrowserWindow, ipcMain } from 'electron/main';
import { ifdescribe, listen } from './lib/spec-helpers';
import { once } from 'node:events';
describe('renderer nodeIntegrationInSubFrames', () => {
2019-08-28 20:55:01 +00:00
const generateTests = (description: string, webPreferences: any) => {
describe(description, () => {
const fixtureSuffix = webPreferences.webviewTag ? '-webview' : '';
2019-08-28 20:55:01 +00:00
let w: BrowserWindow;
beforeEach(async () => {
await closeWindow(w);
w = new BrowserWindow({
show: false,
width: 400,
height: 400,
webPreferences
});
});
2019-08-28 20:55:01 +00:00
afterEach(async () => {
await closeWindow(w);
w = null as unknown as BrowserWindow;
});
it('should load preload scripts in top level iframes', async () => {
2019-06-24 18:41:20 +00:00
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
const [event1, event2] = await detailsPromise;
expect(event1[0].frameId).to.not.equal(event2[0].frameId);
expect(event1[0].frameId).to.equal(event1[2]);
expect(event2[0].frameId).to.equal(event2[2]);
expect(event1[0].senderFrame.routingId).to.equal(event1[2]);
expect(event2[0].senderFrame.routingId).to.equal(event2[2]);
});
it('should load preload scripts in nested iframes', async () => {
2019-06-24 18:41:20 +00:00
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 3);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-with-frame-container${fixtureSuffix}.html`));
const [event1, event2, event3] = await detailsPromise;
expect(event1[0].frameId).to.not.equal(event2[0].frameId);
expect(event1[0].frameId).to.not.equal(event3[0].frameId);
expect(event2[0].frameId).to.not.equal(event3[0].frameId);
expect(event1[0].frameId).to.equal(event1[2]);
expect(event2[0].frameId).to.equal(event2[2]);
expect(event3[0].frameId).to.equal(event3[2]);
expect(event1[0].senderFrame.routingId).to.equal(event1[2]);
expect(event2[0].senderFrame.routingId).to.equal(event2[2]);
expect(event3[0].senderFrame.routingId).to.equal(event3[2]);
});
it('should correctly reply to the main frame with using event.reply', async () => {
2019-06-24 18:41:20 +00:00
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
const [event1] = await detailsPromise;
const pongPromise = once(ipcMain, 'preload-pong');
event1[0].reply('preload-ping');
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event1[0].frameId);
});
it('should correctly reply to the main frame with using event.senderFrame.send', async () => {
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
const [event1] = await detailsPromise;
const pongPromise = once(ipcMain, 'preload-pong');
event1[0].senderFrame.send('preload-ping');
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event1[0].frameId);
});
it('should correctly reply to the sub-frames with using event.reply', async () => {
2019-06-24 18:41:20 +00:00
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
const [, event2] = await detailsPromise;
const pongPromise = once(ipcMain, 'preload-pong');
event2[0].reply('preload-ping');
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event2[0].frameId);
});
it('should correctly reply to the sub-frames with using event.senderFrame.send', async () => {
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
const [, event2] = await detailsPromise;
const pongPromise = once(ipcMain, 'preload-pong');
event2[0].senderFrame.send('preload-ping');
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event2[0].frameId);
});
it('should correctly reply to the nested sub-frames with using event.reply', async () => {
2019-06-24 18:41:20 +00:00
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 3);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-with-frame-container${fixtureSuffix}.html`));
const [, , event3] = await detailsPromise;
const pongPromise = once(ipcMain, 'preload-pong');
event3[0].reply('preload-ping');
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event3[0].frameId);
});
it('should correctly reply to the nested sub-frames with using event.senderFrame.send', async () => {
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 3);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-with-frame-container${fixtureSuffix}.html`));
const [, , event3] = await detailsPromise;
const pongPromise = once(ipcMain, 'preload-pong');
event3[0].senderFrame.send('preload-ping');
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event3[0].frameId);
});
it('should not expose globals in main world', async () => {
2019-06-24 18:41:20 +00:00
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
const details = await detailsPromise;
const senders = details.map(event => event[0].sender);
2020-07-06 17:50:03 +00:00
const isolatedGlobals = await Promise.all(senders.map(sender => sender.executeJavaScript('window.isolatedGlobal')));
refactor: use v8 serialization for ipc (#20214) * refactor: use v8 serialization for ipc * cloning process.env doesn't work * serialize host objects by enumerating key/values * new serialization can handle NaN, Infinity, and undefined correctly * can't allocate v8 objects during GC * backport microtasks fix * fix compile * fix node_stream_loader reentrancy * update subframe spec to expect undefined instead of null * write undefined instead of crashing when serializing host objects * fix webview spec * fix download spec * buffers are transformed into uint8arrays * can't serialize promises * fix chrome.i18n.getMessage * fix devtools tests * fix zoom test * fix debug build * fix lint * update ipcRenderer tests * fix printToPDF test * update patch * remove accidentally re-added remote-side spec * wip * don't attempt to serialize host objects * jump through different hoops to set options.webContents sometimes * whoops * fix lint * clean up error-handling logic * fix memory leak * fix lint * convert host objects using old base::Value serialization * fix lint more * fall back to base::Value-based serialization * remove commented-out code * add docs to breaking-changes.md * Update breaking-changes.md * update ipcRenderer and WebContents docs * lint * use named values for format tag * save a memcpy for ~30% speedup * get rid of calls to ShallowClone * extra debugging for paranoia * d'oh, use the correct named tags * apparently msstl doesn't like this DCHECK * funny story about that DCHECK * disable remote-related functions when enable_remote_module = false * nits * use EnableIf to disable remote methods in mojom * fix include * review comments
2019-10-09 17:59:08 +00:00
for (const result of isolatedGlobals) {
if (webPreferences.contextIsolation === undefined || webPreferences.contextIsolation) {
refactor: use v8 serialization for ipc (#20214) * refactor: use v8 serialization for ipc * cloning process.env doesn't work * serialize host objects by enumerating key/values * new serialization can handle NaN, Infinity, and undefined correctly * can't allocate v8 objects during GC * backport microtasks fix * fix compile * fix node_stream_loader reentrancy * update subframe spec to expect undefined instead of null * write undefined instead of crashing when serializing host objects * fix webview spec * fix download spec * buffers are transformed into uint8arrays * can't serialize promises * fix chrome.i18n.getMessage * fix devtools tests * fix zoom test * fix debug build * fix lint * update ipcRenderer tests * fix printToPDF test * update patch * remove accidentally re-added remote-side spec * wip * don't attempt to serialize host objects * jump through different hoops to set options.webContents sometimes * whoops * fix lint * clean up error-handling logic * fix memory leak * fix lint * convert host objects using old base::Value serialization * fix lint more * fall back to base::Value-based serialization * remove commented-out code * add docs to breaking-changes.md * Update breaking-changes.md * update ipcRenderer and WebContents docs * lint * use named values for format tag * save a memcpy for ~30% speedup * get rid of calls to ShallowClone * extra debugging for paranoia * d'oh, use the correct named tags * apparently msstl doesn't like this DCHECK * funny story about that DCHECK * disable remote-related functions when enable_remote_module = false * nits * use EnableIf to disable remote methods in mojom * fix include * review comments
2019-10-09 17:59:08 +00:00
expect(result).to.be.undefined();
} else {
expect(result).to.equal(true);
}
}
});
});
};
2019-08-28 20:55:01 +00:00
const generateConfigs = (webPreferences: any, ...permutations: {name: string, webPreferences: any}[]) => {
const configs = [{ webPreferences, names: [] as string[] }];
for (const permutation of permutations) {
const length = configs.length;
for (let j = 0; j < length; j++) {
const newConfig = Object.assign({}, configs[j]);
newConfig.webPreferences = Object.assign({},
newConfig.webPreferences, permutation.webPreferences);
newConfig.names = newConfig.names.slice(0);
newConfig.names.push(permutation.name);
configs.push(newConfig);
}
}
2019-08-28 20:55:01 +00:00
return configs.map((config: any) => {
if (config.names.length > 0) {
config.title = `with ${config.names.join(', ')} on`;
} else {
config.title = 'without anything special turned on';
}
delete config.names;
2019-08-28 20:55:01 +00:00
return config as {title: string, webPreferences: any};
});
};
generateConfigs(
{
preload: path.resolve(__dirname, 'fixtures/sub-frames/preload.js'),
nodeIntegrationInSubFrames: true
},
{
name: 'sandbox',
webPreferences: { sandbox: true }
},
{
name: 'context isolation disabled',
webPreferences: { contextIsolation: false }
},
{
name: 'webview',
webPreferences: { webviewTag: true, preload: false }
}
).forEach(config => {
generateTests(config.title, config.webPreferences);
});
describe('internal <iframe> inside of <webview>', () => {
2019-08-28 20:55:01 +00:00
let w: BrowserWindow;
beforeEach(async () => {
await closeWindow(w);
w = new BrowserWindow({
show: false,
width: 400,
height: 400,
webPreferences: {
preload: path.resolve(__dirname, 'fixtures/sub-frames/webview-iframe-preload.js'),
nodeIntegrationInSubFrames: true,
webviewTag: true,
contextIsolation: false
}
});
});
2019-08-28 20:55:01 +00:00
afterEach(async () => {
await closeWindow(w);
w = null as unknown as BrowserWindow;
});
it('should not load preload scripts', async () => {
const promisePass = once(ipcMain, 'webview-loaded');
const promiseFail = once(ipcMain, 'preload-in-frame').then(() => {
throw new Error('preload loaded in internal frame');
});
await w.loadURL('about:blank');
return Promise.race([promisePass, promiseFail]);
});
});
});
2019-08-28 20:55:01 +00:00
// app.getAppMetrics() does not return sandbox information on Linux.
ifdescribe(process.platform !== 'linux')('cross-site frame sandboxing', () => {
let server: http.Server;
let crossSiteUrl: string;
let serverUrl: string;
before(async function () {
server = http.createServer((req, res) => {
2019-08-28 20:55:01 +00:00
res.end(`<iframe name="frame" src="${crossSiteUrl}" />`);
});
serverUrl = (await listen(server)).url;
crossSiteUrl = serverUrl.replace('127.0.0.1', 'localhost');
});
after(() => {
server.close();
2019-08-28 20:55:01 +00:00
server = null as unknown as http.Server;
});
2019-08-28 20:55:01 +00:00
let w: BrowserWindow;
2019-08-28 20:55:01 +00:00
afterEach(async () => {
await closeWindow(w);
w = null as unknown as BrowserWindow;
});
2019-08-28 20:55:01 +00:00
const generateSpecs = (description: string, webPreferences: any) => {
describe(description, () => {
it('iframe process is sandboxed if possible', async () => {
w = new BrowserWindow({
show: false,
webPreferences
});
2019-08-28 20:55:01 +00:00
await w.loadURL(serverUrl);
const pidMain = w.webContents.getOSProcessId();
const pidFrame = w.webContents.mainFrame.frames.find(f => f.name === 'frame')!.osProcessId;
const metrics = app.getAppMetrics();
2019-08-28 20:55:01 +00:00
const isProcessSandboxed = function (pid: number) {
const entry = metrics.find(metric => metric.pid === pid);
return entry && entry.sandboxed;
};
const sandboxMain = !!(webPreferences.sandbox || process.mas);
const sandboxFrame = sandboxMain || !webPreferences.nodeIntegrationInSubFrames;
expect(isProcessSandboxed(pidMain)).to.equal(sandboxMain);
expect(isProcessSandboxed(pidFrame)).to.equal(sandboxFrame);
});
});
};
generateSpecs('nodeIntegrationInSubFrames = false, sandbox = false', {
nodeIntegrationInSubFrames: false,
sandbox: false
});
generateSpecs('nodeIntegrationInSubFrames = false, sandbox = true', {
nodeIntegrationInSubFrames: false,
sandbox: true
});
generateSpecs('nodeIntegrationInSubFrames = true, sandbox = false', {
nodeIntegrationInSubFrames: true,
sandbox: false
});
generateSpecs('nodeIntegrationInSubFrames = true, sandbox = true', {
nodeIntegrationInSubFrames: true,
sandbox: true
});
});