fix(extensions): implement missing web_request hooks (#22655)

Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
Co-authored-by: samuelmaddock <samuel.maddock@gmail.com>
This commit is contained in:
Eryk Rakowski 2020-12-19 00:11:43 +01:00 committed by GitHub
parent 8f1bc338e5
commit bf24759354
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 216 additions and 25 deletions

View file

@ -1,12 +1,15 @@
import { expect } from 'chai';
import { app, session, BrowserWindow, ipcMain, WebContents, Extension } from 'electron/main';
import { app, session, BrowserWindow, ipcMain, WebContents, Extension, Session } from 'electron/main';
import { closeAllWindows, closeWindow } from './window-helpers';
import * as http from 'http';
import { AddressInfo } from 'net';
import * as path from 'path';
import * as fs from 'fs';
import * as WebSocket from 'ws';
import { emittedOnce, emittedNTimes } from './events-helpers';
const uuid = require('uuid');
const fixtures = path.join(__dirname, 'fixtures');
describe('chrome extensions', () => {
@ -15,6 +18,7 @@ describe('chrome extensions', () => {
// NB. extensions are only allowed on http://, https:// and ftp:// (!) urls by default.
let server: http.Server;
let url: string;
let port: string;
before(async () => {
server = http.createServer((req, res) => {
if (req.url === '/cors') {
@ -22,8 +26,19 @@ describe('chrome extensions', () => {
}
res.end(emptyPage);
});
const wss = new WebSocket.Server({ noServer: true });
wss.on('connection', function connection (ws) {
ws.on('message', function incoming (message) {
if (message === 'foo') {
ws.send('bar');
}
});
});
await new Promise(resolve => server.listen(0, '127.0.0.1', () => {
url = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
port = String((server.address() as AddressInfo).port);
url = `http://127.0.0.1:${port}`;
resolve();
}));
});
@ -84,7 +99,7 @@ describe('chrome extensions', () => {
// extension registry is redirected to the main session. so installing an
// extension in an in-memory session results in it being installed in the
// default session.
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
await w.loadURL(url);
@ -95,7 +110,7 @@ describe('chrome extensions', () => {
it('serializes a loaded extension', async () => {
const extensionPath = path.join(fixtures, 'extensions', 'red-bg');
const manifest = JSON.parse(fs.readFileSync(path.join(extensionPath, 'manifest.json'), 'utf-8'));
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
const extension = await customSession.loadExtension(extensionPath);
expect(extension.id).to.be.a('string');
expect(extension.name).to.be.a('string');
@ -106,7 +121,7 @@ describe('chrome extensions', () => {
});
it('removes an extension', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg'));
{
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
@ -141,7 +156,7 @@ describe('chrome extensions', () => {
});
it('lists loaded extensions in getAllExtensions', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
const e = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg'));
expect(customSession.getAllExtensions()).to.deep.equal([e]);
customSession.removeExtension(e.id);
@ -149,13 +164,13 @@ describe('chrome extensions', () => {
});
it('gets an extension by id', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
const e = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg'));
expect(customSession.getExtension(e.id)).to.deep.equal(e);
});
it('confines an extension to the session it was loaded in', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg'));
const w = new BrowserWindow({ show: false }); // not in the session
await w.loadURL(url);
@ -164,7 +179,7 @@ describe('chrome extensions', () => {
});
it('loading an extension in a temporary session throws an error', async () => {
const customSession = session.fromPartition(require('uuid').v4());
const customSession = session.fromPartition(uuid.v4());
await expect(customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg'))).to.eventually.be.rejectedWith('Extensions cannot be loaded in a temporary session');
});
@ -178,7 +193,7 @@ describe('chrome extensions', () => {
return result;
};
beforeEach(async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-i18n'));
w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } });
await w.loadURL(url);
@ -203,7 +218,7 @@ describe('chrome extensions', () => {
return result;
};
beforeEach(async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-runtime'));
w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } });
await w.loadURL(url);
@ -231,7 +246,7 @@ describe('chrome extensions', () => {
describe('chrome.storage', () => {
it('stores and retrieves a key', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-storage'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } });
try {
@ -245,9 +260,75 @@ describe('chrome extensions', () => {
});
});
describe('chrome.webRequest', () => {
function fetch (contents: WebContents, url: string) {
return contents.executeJavaScript(`fetch(${JSON.stringify(url)})`);
}
let customSession: Session;
let w: BrowserWindow;
beforeEach(() => {
customSession = session.fromPartition(`persist:${uuid.v4()}`);
w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true, contextIsolation: true } });
});
describe('onBeforeRequest', () => {
it('can cancel http requests', async () => {
await w.loadURL(url);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest'));
await expect(fetch(w.webContents, url)).to.eventually.be.rejectedWith(TypeError);
});
it('does not cancel http requests when no extension loaded', async () => {
await w.loadURL(url);
await expect(fetch(w.webContents, url)).to.not.be.rejectedWith(TypeError);
});
});
it('does not take precedence over Electron webRequest - http', async () => {
return new Promise((resolve) => {
(async () => {
customSession.webRequest.onBeforeRequest((details, callback) => {
resolve();
callback({ cancel: true });
});
await w.loadURL(url);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest'));
fetch(w.webContents, url);
})();
});
});
it('does not take precedence over Electron webRequest - WebSocket', () => {
return new Promise((resolve) => {
(async () => {
customSession.webRequest.onBeforeSendHeaders(() => {
resolve();
});
await w.loadFile(path.join(fixtures, 'api', 'webrequest.html'), { query: { port } });
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest-wss'));
})();
});
});
describe('WebSocket', () => {
it('can be proxied', async () => {
await w.loadFile(path.join(fixtures, 'api', 'webrequest.html'), { query: { port } });
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest-wss'));
customSession.webRequest.onSendHeaders((details) => {
if (details.url.startsWith('ws://')) {
expect(details.requestHeaders.foo).be.equal('bar');
}
});
});
});
});
describe('chrome.tabs', () => {
it('executeScript', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-api'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } });
await w.loadURL(url);
@ -262,12 +343,12 @@ describe('chrome extensions', () => {
});
it('connect', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-api'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } });
await w.loadURL(url);
const portName = require('uuid').v4();
const portName = uuid.v4();
const message = { method: 'connectTab', args: [portName] };
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
@ -278,7 +359,7 @@ describe('chrome extensions', () => {
});
it('sendMessage receives the response', async function () {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-api'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } });
await w.loadURL(url);
@ -296,7 +377,7 @@ describe('chrome extensions', () => {
describe('background pages', () => {
it('loads a lazy background page when sending a message', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true } });
try {
@ -312,7 +393,7 @@ describe('chrome extensions', () => {
});
it('can use extension.getBackgroundPage from a ui page', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
await w.loadURL(`chrome-extension://${id}/page-get-background.html`);
@ -321,7 +402,7 @@ describe('chrome extensions', () => {
});
it('can use extension.getBackgroundPage from a ui page', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
await w.loadURL(`chrome-extension://${id}/page-get-background.html`);
@ -330,7 +411,7 @@ describe('chrome extensions', () => {
});
it('can use runtime.getBackgroundPage from a ui page', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'lazy-background-page'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
await w.loadURL(`chrome-extension://${id}/page-runtime-get-background.html`);
@ -390,7 +471,7 @@ describe('chrome extensions', () => {
};
it('loads a devtools extension', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
const customSession = session.fromPartition(`persist:${uuid.v4()}`);
customSession.loadExtension(path.join(fixtures, 'extensions', 'devtools-extension'));
const winningMessage = emittedOnce(ipcMain, 'winning');
const w = new BrowserWindow({ show: true, webPreferences: { session: customSession, nodeIntegration: true } });