|
@@ -8,6 +8,8 @@ import * as http from 'node:http';
|
|
|
import * as fs from 'node:fs';
|
|
|
import * as qs from 'node:querystring';
|
|
|
import * as stream from 'node:stream';
|
|
|
+import * as streamConsumers from 'node:stream/consumers';
|
|
|
+import * as webStream from 'node:stream/web';
|
|
|
import { EventEmitter, once } from 'node:events';
|
|
|
import { closeAllWindows, closeWindow } from './lib/window-helpers';
|
|
|
import { WebmGenerator } from './lib/video-helpers';
|
|
@@ -1548,6 +1550,122 @@ describe('protocol module', () => {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
+ it('does not emit undefined chunks into the request body stream when uploading a stream', async () => {
|
|
|
+ protocol.handle('cors', async (request) => {
|
|
|
+ expect(request.body).to.be.an.instanceOf(webStream.ReadableStream);
|
|
|
+ for await (const value of request.body as webStream.ReadableStream<Uint8Array>) {
|
|
|
+ expect(value).to.not.be.undefined();
|
|
|
+ }
|
|
|
+ return new Response(undefined, { status: 200 });
|
|
|
+ });
|
|
|
+ defer(() => { protocol.unhandle('cors'); });
|
|
|
+
|
|
|
+ await contents.loadFile(path.resolve(fixturesPath, 'pages', 'base-page.html'));
|
|
|
+ contents.on('console-message', (e, level, message) => console.log(message));
|
|
|
+ const ok = await contents.executeJavaScript(`(async () => {
|
|
|
+ function wait(milliseconds) {
|
|
|
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
|
+ }
|
|
|
+
|
|
|
+ const stream = new ReadableStream({
|
|
|
+ async start(controller) {
|
|
|
+ await wait(4);
|
|
|
+ controller.enqueue('This ');
|
|
|
+ await wait(4);
|
|
|
+ controller.enqueue('is ');
|
|
|
+ await wait(4);
|
|
|
+ controller.enqueue('a ');
|
|
|
+ await wait(4);
|
|
|
+ controller.enqueue('slow ');
|
|
|
+ await wait(4);
|
|
|
+ controller.enqueue('request.');
|
|
|
+ controller.close();
|
|
|
+ }
|
|
|
+ }).pipeThrough(new TextEncoderStream());
|
|
|
+ return (await fetch('cors://url.invalid', { method: 'POST', body: stream, duplex: 'half' })).ok;
|
|
|
+ })()`);
|
|
|
+ expect(ok).to.be.true();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('does not emit undefined chunks into the request body stream when uploading a file', async () => {
|
|
|
+ protocol.handle('cors', async (request) => {
|
|
|
+ expect(request.body).to.be.an.instanceOf(webStream.ReadableStream);
|
|
|
+ for await (const value of request.body as webStream.ReadableStream<Uint8Array>) {
|
|
|
+ expect(value).to.not.be.undefined();
|
|
|
+ }
|
|
|
+ return new Response(undefined, { status: 200 });
|
|
|
+ });
|
|
|
+ defer(() => { protocol.unhandle('cors'); });
|
|
|
+
|
|
|
+ await contents.loadFile(path.resolve(fixturesPath, 'pages', 'file-input.html'));
|
|
|
+ const { debugger: debug } = contents;
|
|
|
+ debug.attach();
|
|
|
+ try {
|
|
|
+ const { root: { nodeId } } = await debug.sendCommand('DOM.getDocument');
|
|
|
+ const { nodeId: inputNodeId } = await debug.sendCommand('DOM.querySelector', { nodeId, selector: 'input' });
|
|
|
+ await debug.sendCommand('DOM.setFileInputFiles', {
|
|
|
+ files: [path.join(fixturesPath, 'cat-spin.mp4')],
|
|
|
+ nodeId: inputNodeId
|
|
|
+ });
|
|
|
+ const ok = await contents.executeJavaScript(`(async () => {
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append("data", document.getElementById("file").files[0]);
|
|
|
+ return (await fetch('cors://url.invalid', { method: 'POST', body: formData })).ok;
|
|
|
+ })()`);
|
|
|
+ expect(ok).to.be.true();
|
|
|
+ } finally {
|
|
|
+ debug.detach();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('filters an illegal "origin: null" header', async () => {
|
|
|
+ protocol.handle('http', (req) => {
|
|
|
+ expect(new Headers(req.headers).get('origin')).to.not.equal('null');
|
|
|
+ return new Response();
|
|
|
+ });
|
|
|
+ defer(() => { protocol.unhandle('http'); });
|
|
|
+
|
|
|
+ const filePath = path.join(fixturesPath, 'pages', 'form-with-data.html');
|
|
|
+ await contents.loadFile(filePath);
|
|
|
+
|
|
|
+ const loadPromise = new Promise((resolve, reject) => {
|
|
|
+ contents.once('did-finish-load', resolve);
|
|
|
+ contents.once('did-fail-load', (_, errorCode, errorDescription) =>
|
|
|
+ reject(new Error(`did-fail-load: ${errorCode} ${errorDescription}. See AssertionError for details.`))
|
|
|
+ );
|
|
|
+ });
|
|
|
+ await contents.executeJavaScript(`
|
|
|
+ const form = document.querySelector('form');
|
|
|
+ form.action = 'http://cors.invalid';
|
|
|
+ form.method = 'POST';
|
|
|
+ form.submit();
|
|
|
+ `);
|
|
|
+ await loadPromise;
|
|
|
+ });
|
|
|
+
|
|
|
+ it('does forward Blob chunks', async () => {
|
|
|
+ // we register the protocol on a separate session to validate the assumption
|
|
|
+ // that `getBlobData()` indeed returns the blob data from a global variable
|
|
|
+ const s = session.fromPartition('protocol-handle-forwards-blob-chunks');
|
|
|
+
|
|
|
+ s.protocol.handle('cors', async (request) => {
|
|
|
+ expect(request.body).to.be.an.instanceOf(webStream.ReadableStream);
|
|
|
+ return new Response(
|
|
|
+ `hello to ${await streamConsumers.text(request.body as webStream.ReadableStream<Uint8Array>)}`,
|
|
|
+ { status: 200 }
|
|
|
+ );
|
|
|
+ });
|
|
|
+ defer(() => { s.protocol.unhandle('cors'); });
|
|
|
+
|
|
|
+ const w = new BrowserWindow({ show: false, webPreferences: { session: s } });
|
|
|
+ await w.webContents.loadFile(path.resolve(fixturesPath, 'pages', 'base-page.html'));
|
|
|
+ const response = await w.webContents.executeJavaScript(`(async () => {
|
|
|
+ const body = new Blob(["it's-a ", 'me! ', 'Mario!'], { type: 'text/plain' });
|
|
|
+ return await (await fetch('cors://url.invalid', { method: 'POST', body })).text();
|
|
|
+ })()`);
|
|
|
+ expect(response).to.be.string('hello to it\'s-a me! Mario!');
|
|
|
+ });
|
|
|
+
|
|
|
// TODO(nornagon): this test doesn't pass on Linux currently, investigate.
|
|
|
ifit(process.platform !== 'linux')('is fast', async () => {
|
|
|
// 128 MB of spaces.
|