// Exercise the actual YCode WebContentsView host against a loopback-only HTTP server. if (!process.versions.electron) { const path = require('node:path') require('esbuild').buildSync({ entryPoints: ['src/main/browser-host.ts'], platform: 'node', format: 'cjs', bundle: true, external: ['electron'], outfile: '.tmp/browser-host-test-module.cjs', logLevel: 'silent' }) require('esbuild').buildSync({ entryPoints: ['src/shared/browser-host.ts'], platform: 'node', format: 'cjs', bundle: true, outfile: '.tmp/browser-host-contract.cjs', logLevel: 'silent' }) const env = { ...process.env } delete env.ELECTRON_RUN_AS_NODE const result = require('node:child_process').spawnSync(require('electron'), [__filename], { env, windowsHide: true, stdio: 'inherit', timeout: 60000 }) process.exitCode = result.status ?? 1 } else { const { app, BrowserWindow, WebContentsView } = require('electron') const assert = require('node:assert/strict') const path = require('node:path') const http = require('node:http') app.setPath('userData', path.resolve('.tmp/browser-host-regression-data')) app.disableHardwareAcceleration() const server = http.createServer((req, res) => { const page = req.url === '/b' ? 'B' : 'A' res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }) res.end(`Local ${page}Local ${page}Next`) }) let owner, other async function waitFor(check, label) { for (let attempt = 0; attempt < 200; attempt++) { if (await check()) return; await new Promise(resolve => setTimeout(resolve, 25)) } throw Error('Browser condition timed out: ' + label) } app.whenReady().then(async () => { const host = require(path.resolve('.tmp/browser-host-test-module.cjs')) const contract = require(path.resolve('.tmp/browser-host-contract.cjs')) assert.equal(contract.normalizeBrowserAddressInput('127.0.0.1:1234/a'), 'http://127.0.0.1:1234/a') assert.equal(contract.normalizeBrowserAddressInput('example.com'), 'https://example.com/') assert.equal(contract.normalizeBrowserAddressInput('hello world'), 'https://www.sogou.com/web?query=hello%20world') assert.throws(() => contract.normalizeBrowserHostUrl('https://user:secret@example.com'), /仅支持/) await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) const base = `http://127.0.0.1:${server.address().port}` owner = new BrowserWindow({ width: 800, height: 600, show: false, webPreferences: { sandbox: true, nodeIntegration: false, contextIsolation: true } }) other = new BrowserWindow({ width: 800, height: 600, show: false }) const first = host.createBrowser(owner, 'task-a') const second = host.createBrowser(owner, 'task-b') const result = await host.navigateBrowser(owner, first.id, base + '/a', 'task-a') assert.equal(result.title, 'Local A') assert.equal(result.error, undefined) assert.throws(() => host.getBrowserState(owner, first.id, 'task-b'), /不属于/) assert.throws(() => host.getBrowserState(other, first.id, 'task-a'), /不属于/) assert.throws(() => host.getBrowserState(owner, first.id), /不属于/) await assert.rejects(() => host.navigateBrowser(owner, first.id, 'file:///C:/Windows/win.ini', 'task-a'), /仅支持/) await assert.rejects(() => host.navigateBrowser(owner, first.id, 'javascript:alert(1)', 'task-a'), /仅支持/) assert.equal(host.getBrowserState(owner, first.id, 'task-a').url, base + '/a') const views = owner.contentView.children.filter(view => view instanceof WebContentsView) assert.equal(views.length, 2) const primary = views.find(view => view.webContents.getURL() === base + '/a') const secondary = views.find(view => view !== primary) const secondaryContents = secondary.webContents const prefs = primary.webContents.getLastWebPreferences() assert.equal(prefs.sandbox, true); assert.equal(prefs.nodeIntegration, false); assert.equal(prefs.contextIsolation, true) assert.ok(!prefs.preload) assert.equal(await primary.webContents.executeJavaScript('typeof require'), 'undefined') assert.equal(await primary.webContents.executeJavaScript('typeof process'), 'undefined') assert.equal(await primary.webContents.executeJavaScript('Notification.requestPermission()'), 'denied') await primary.webContents.session.cookies.set({ url: base, name: 'isolation', value: 'private-to-first' }) assert.equal((await secondary.webContents.session.cookies.get({ url: base, name: 'isolation' })).length, 0) host.setBrowserBounds(owner, first.id, { x: -20, y: 50, width: 2000, height: 2000 }, 'task-a') const [width, height] = owner.getContentSize() assert.deepEqual(primary.getBounds(), { x: 0, y: 50, width, height: height - 50 }) assert.equal(host.getBrowserState(owner, first.id, 'task-a').visible, true) assert.throws(() => host.setBrowserBounds(owner, first.id, { x: .5, y: 0, width: 10, height: 10 }, 'task-a'), /整数/) host.hideBrowser(owner, first.id, 'task-a') assert.equal(host.getBrowserState(owner, first.id, 'task-a').visible, false) await host.navigateBrowser(owner, first.id, base + '/b', 'task-a') assert.equal(host.getBrowserState(owner, first.id, 'task-a').canGoBack, true) host.backBrowser(owner, first.id, 'task-a') await waitFor(() => host.getBrowserState(owner, first.id, 'task-a').title === 'Local A' && !host.getBrowserState(owner, first.id, 'task-a').loading, 'back to A') assert.equal(host.getBrowserState(owner, first.id, 'task-a').canGoForward, true) host.forwardBrowser(owner, first.id, 'task-a') await waitFor(() => host.getBrowserState(owner, first.id, 'task-a').title === 'Local B' && !host.getBrowserState(owner, first.id, 'task-a').loading, 'forward to B') host.reloadBrowser(owner, first.id, 'task-a') await waitFor(() => !host.getBrowserState(owner, first.id, 'task-a').loading, 'reload') await primary.webContents.executeJavaScript(`window.open(${JSON.stringify(base + '/a')}, '_blank'); undefined`) await waitFor(() => host.getBrowserState(owner, first.id, 'task-a').title === 'Local A', 'popup stays in owned browser') assert.equal(BrowserWindow.getAllWindows().length, 2) host.closeBrowser(owner, first.id, 'task-a') assert.throws(() => host.getBrowserState(owner, first.id, 'task-a'), /不存在/) const draft = host.createBrowser(owner) assert.throws(() => host.rebindBrowser(other, draft.id, undefined, 'created-session'), /不属于/) const rebound = host.rebindBrowser(owner, draft.id, undefined, 'created-session') assert.equal(rebound.sessionId, 'created-session') assert.equal(rebound.id, draft.id) assert.throws(() => host.getBrowserState(owner, draft.id), /不属于/) assert.equal(host.getBrowserState(owner, draft.id, 'created-session').sessionId, 'created-session') host.closeBrowser(owner, draft.id, 'created-session') owner.destroy() await waitFor(() => secondaryContents.isDestroyed(), 'owner closes child browser') assert.throws(() => host.getBrowserState(owner, second.id, 'task-b'), /不存在/) console.log('Actual Electron browser host: loopback navigation/back/forward/reload, sandbox, cookie/window/session isolation, bounds, popup routing and lifecycle passed') }).catch(error => { console.error(error); process.exitCode = 1 }).finally(() => { if (owner && !owner.isDestroyed()) owner.destroy() if (other && !other.isDestroyed()) other.destroy() server.close(() => app.exit(process.exitCode || 0)) }) }