electron/spec/esm-spec.ts
electron-roller[bot] 890a557eed
chore: bump node to v20.10.0 (main) (#40675)
* chore: bump node in DEPS to v20.10.0

* chore: update feat_initialize_asar_support.patch

no code changes; patch just needed an update due to nearby upstream changes

Xref: https://github.com/nodejs/node/pull/49986

* chore: update pass_all_globals_through_require.patch

no manual changes; patch applied with fuzz

Xref: https://github.com/nodejs/node/pull/49657

* chore: update refactor_allow_embedder_overriding_of_internal_fs_calls

Xref: https://github.com/nodejs/node/pull/49912

no code changes; patch just needed an update due to nearby upstream changes

* chore: update chore_allow_the_node_entrypoint_to_be_a_builtin_module.patch

Xref: https://github.com/nodejs/node/pull/49986

minor manual changes needed to sync with upstream change

* update fix_expose_the_built-in_electron_module_via_the_esm_loader.patch

Xref: https://github.com/nodejs/node/pull/50096
Xref: https://github.com/nodejs/node/pull/50314
in lib/internal/modules/esm/load.js, update the code that checks for
`format === 'electron'`. I'd like 👀 on this

Xref: https://github.com/nodejs/node/pull/49657
add braces in lib/internal/modules/esm/translators.js to sync with upstream

* fix: lazyload fs in esm loaders to apply asar patches

* https://github.com/nodejs/node/pull/50127
* https://github.com/nodejs/node/pull/50096

* esm: jsdoc for modules code

https://github.com/nodejs/node/pull/49523

* test: set test-cli-node-options as flaky

https://github.com/nodejs/node/pull/50296

* deps: update c-ares to 1.20.1

https://github.com/nodejs/node/pull/50082

* esm: bypass CommonJS loader under --default-type=module

https://github.com/nodejs/node/pull/49986

* deps: update uvwasi to 0.0.19

https://github.com/nodejs/node/pull/49908

* lib,test: do not hardcode Buffer.kMaxLength

https://github.com/nodejs/node/pull/49876

* crypto: account for disabled SharedArrayBuffer

https://github.com/nodejs/node/pull/50034

* test: fix edge snapshot stack traces

https://github.com/nodejs/node/pull/49659

* src: generate snapshot with --predictable

https://github.com/nodejs/node/pull/48749

* chore: fixup patch indices

* fs: throw errors from sync branches instead of separate implementations

https://github.com/nodejs/node/pull/49913

* crypto: ensure valid point on elliptic curve in SubtleCrypto.importKey

https://github.com/nodejs/node/pull/50234

* esm: detect ESM syntax in ambiguous JavaScrip

https://github.com/nodejs/node/pull/50096

* fixup! test: fix edge snapshot stack traces

* esm: unflag extensionless ES module JavaScript and Wasm in module scope

https://github.com/nodejs/node/pull/49974

* [tagged-ptr] Arrowify objects

https://chromium-review.googlesource.com/c/v8/v8/+/4705331

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-12-11 21:09:50 +01:00

209 lines
7.3 KiB
TypeScript

import { expect } from 'chai';
import * as cp from 'node:child_process';
import { BrowserWindow } from 'electron';
import * as fs from 'fs-extra';
import * as os from 'node:os';
import * as path from 'node:path';
import { pathToFileURL } from 'node:url';
const runFixture = async (appPath: string, args: string[] = []) => {
const result = cp.spawn(process.execPath, [appPath, ...args], {
stdio: 'pipe'
});
const stdout: Buffer[] = [];
const stderr: Buffer[] = [];
result.stdout.on('data', (chunk) => stdout.push(chunk));
result.stderr.on('data', (chunk) => stderr.push(chunk));
const [code, signal] = await new Promise<[number | null, NodeJS.Signals | null]>((resolve) => {
result.on('close', (code, signal) => {
resolve([code, signal]);
});
});
return {
code,
signal,
stdout: Buffer.concat(stdout).toString().trim(),
stderr: Buffer.concat(stderr).toString().trim()
};
};
const fixturePath = path.resolve(__dirname, 'fixtures', 'esm');
describe('esm', () => {
describe('main process', () => {
it('should load an esm entrypoint', async () => {
const result = await runFixture(path.resolve(fixturePath, 'entrypoint.mjs'));
expect(result.code).to.equal(0);
expect(result.stdout).to.equal('ESM Launch, ready: false');
});
it('should load an esm entrypoint based on type=module', async () => {
const result = await runFixture(path.resolve(fixturePath, 'package'));
expect(result.code).to.equal(0);
expect(result.stdout).to.equal('ESM Package Launch, ready: false');
});
it('should wait for a top-level await before declaring the app ready', async () => {
const result = await runFixture(path.resolve(fixturePath, 'top-level-await.mjs'));
expect(result.code).to.equal(0);
expect(result.stdout).to.equal('Top level await, ready: false');
});
it('should allow usage of pre-app-ready apis in top-level await', async () => {
const result = await runFixture(path.resolve(fixturePath, 'pre-app-ready-apis.mjs'));
expect(result.code).to.equal(0);
});
it('should allow use of dynamic import', async () => {
const result = await runFixture(path.resolve(fixturePath, 'dynamic.mjs'));
expect(result.code).to.equal(0);
expect(result.stdout).to.equal('Exit with app, ready: false');
});
});
describe('renderer process', () => {
let w: BrowserWindow | null = null;
const tempDirs: string[] = [];
afterEach(async () => {
if (w) w.close();
w = null;
while (tempDirs.length) {
await fs.remove(tempDirs.pop()!);
}
});
async function loadWindowWithPreload (preload: string, webPreferences: Electron.WebPreferences) {
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'e-spec-preload-'));
tempDirs.push(tmpDir);
const preloadPath = path.resolve(tmpDir, 'preload.mjs');
await fs.writeFile(preloadPath, preload);
w = new BrowserWindow({
show: false,
webPreferences: {
...webPreferences,
preload: preloadPath
}
});
let error: Error | null = null;
w.webContents.on('preload-error', (_, __, err) => {
error = err;
});
await w.loadFile(path.resolve(fixturePath, 'empty.html'));
return [w.webContents, error] as [Electron.WebContents, Error | null];
}
describe('nodeIntegration', () => {
it('should support an esm entrypoint', async () => {
const [webContents] = await loadWindowWithPreload('import { resolve } from "path"; window.resolvePath = resolve;', {
nodeIntegration: true,
sandbox: false,
contextIsolation: false
});
const exposedType = await webContents.executeJavaScript('typeof window.resolvePath');
expect(exposedType).to.equal('function');
});
it('should delay load until the ESM import chain is complete', async () => {
const [webContents] = await loadWindowWithPreload(`import { resolve } from "path";
await new Promise(r => setTimeout(r, 500));
window.resolvePath = resolve;`, {
nodeIntegration: true,
sandbox: false,
contextIsolation: false
});
const exposedType = await webContents.executeJavaScript('typeof window.resolvePath');
expect(exposedType).to.equal('function');
});
it('should support a top-level await fetch blocking the page load', async () => {
const [webContents] = await loadWindowWithPreload(`
const r = await fetch("package/package.json");
window.packageJson = await r.json();`, {
nodeIntegration: true,
sandbox: false,
contextIsolation: false
});
const packageJson = await webContents.executeJavaScript('window.packageJson');
expect(packageJson).to.deep.equal(require('./fixtures/esm/package/package.json'));
});
const hostsUrl = pathToFileURL(process.platform === 'win32' ? 'C:\\Windows\\System32\\drivers\\etc\\hosts' : '/etc/hosts');
describe('without context isolation', () => {
it('should use blinks dynamic loader in the main world', async () => {
const [webContents] = await loadWindowWithPreload('', {
nodeIntegration: true,
sandbox: false,
contextIsolation: false
});
let error: Error | null = null;
try {
await webContents.executeJavaScript(`import(${JSON.stringify(hostsUrl)})`);
} catch (err) {
error = err as Error;
}
expect(error).to.not.equal(null);
// This is a blink specific error message
expect(error?.message).to.include('Failed to fetch dynamically imported module');
});
});
describe('with context isolation', () => {
let badFilePath = '';
beforeEach(async () => {
badFilePath = path.resolve(path.resolve(os.tmpdir(), 'bad-file.badjs'));
await fs.promises.writeFile(badFilePath, 'const foo = "bar";');
});
afterEach(async () => {
await fs.promises.unlink(badFilePath);
});
it('should use nodes esm dynamic loader in the isolated context', async () => {
const [, preloadError] = await loadWindowWithPreload(`await import(${JSON.stringify((pathToFileURL(badFilePath)))})`, {
nodeIntegration: true,
sandbox: false,
contextIsolation: true
});
expect(preloadError).to.not.equal(null);
// This is a node.js specific error message
expect(preloadError!.toString()).to.include('Unknown file extension');
});
it('should use blinks dynamic loader in the main world', async () => {
const [webContents] = await loadWindowWithPreload('', {
nodeIntegration: true,
sandbox: false,
contextIsolation: true
});
let error: Error | null = null;
try {
await webContents.executeJavaScript(`import(${JSON.stringify(hostsUrl)})`);
} catch (err) {
error = err as Error;
}
expect(error).to.not.equal(null);
// This is a blink specific error message
expect(error?.message).to.include('Failed to fetch dynamically imported module');
});
});
});
});
});