106 lines
6.1 KiB
HTML
106 lines
6.1 KiB
HTML
<!doctype html>
|
|
<meta charset="utf-8">
|
|
<title>OCCT Browser Harness</title>
|
|
<pre id="result">running</pre>
|
|
<script type="module">
|
|
const output = document.querySelector('#result')
|
|
let lastWorkerEvent = null
|
|
const MiB = 1024 * 1024
|
|
const memoryBudgets = { wasmPeakBytes: 256 * MiB, wasmRetainedBytes: 16 * MiB, browserRetainedBytes: 64 * MiB, gpuLiveResources: 0 }
|
|
const measureBrowserMemory = async () => {
|
|
if (typeof performance.measureUserAgentSpecificMemory === 'function') {
|
|
try {
|
|
const measurement = await performance.measureUserAgentSpecificMemory()
|
|
return { api: 'measureUserAgentSpecificMemory', bytes: measurement.bytes }
|
|
} catch (error) {
|
|
// Chrome may expose the API while disabling it in a particular headless mode.
|
|
}
|
|
}
|
|
if (performance.memory && Number.isFinite(performance.memory.usedJSHeapSize)) return { api: 'performance.memory.usedJSHeapSize', bytes: performance.memory.usedJSHeapSize }
|
|
return { api: 'unavailable', bytes: null }
|
|
}
|
|
const waitFor = (worker, type, timeoutMs = 180000) => new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => reject(new Error(`${type} timeout; last=${JSON.stringify(lastWorkerEvent)}`)), timeoutMs)
|
|
const listener = (event) => {
|
|
lastWorkerEvent = event.data
|
|
if (event.data.type === 'error') { clearTimeout(timer); worker.removeEventListener('message', listener); reject(new Error(event.data.error)); return }
|
|
if (event.data.type !== type) return
|
|
clearTimeout(timer); worker.removeEventListener('message', listener); resolve(event.data)
|
|
}
|
|
const errorListener = (event) => { clearTimeout(timer); worker.removeEventListener('message', listener); worker.removeEventListener('error', errorListener); reject(new Error(event.message || `${type} worker error`)) }
|
|
worker.addEventListener('message', listener)
|
|
worker.addEventListener('error', errorListener)
|
|
})
|
|
|
|
const run = async () => {
|
|
const browserId = new URL(location.href).searchParams.get('browser') || 'unknown'
|
|
const result = { schemaVersion: 1, browserId, crossOriginIsolated: globalThis.crossOriginIsolated === true, browser: navigator.userAgent, opfs: 'pending', initialize: 'pending', history: 'pending', memory: 'pending', abort: 'pending', crashRecovery: 'pending' }
|
|
const verifyOpfs = async () => {
|
|
const opfsRoot = await navigator.storage.getDirectory()
|
|
const opfsDirectory = await opfsRoot.getDirectoryHandle('bitbybit-browser-gate', { create: true })
|
|
const opfsFile = await opfsDirectory.getFileHandle(`${browserId}.probe`, { create: true })
|
|
const opfsWritable = await opfsFile.createWritable()
|
|
await opfsWritable.write(`OPFS-${browserId}`)
|
|
await opfsWritable.close()
|
|
const opfsText = await (await opfsFile.getFile()).text()
|
|
if (opfsText !== `OPFS-${browserId}`) throw new Error('OPFS read-after-write verification failed.')
|
|
await opfsDirectory.removeEntry(`${browserId}.probe`)
|
|
return { status: 'pass', persistence: 'opfs', bytes: opfsText.length }
|
|
}
|
|
try {
|
|
result.opfs = await Promise.race([verifyOpfs(), new Promise((_, reject) => setTimeout(() => reject(new Error('OPFS API did not respond within the browser capability window.')), 5000))])
|
|
} catch (error) {
|
|
throw error
|
|
}
|
|
let worker = new Worker('/native/occt-history/browser-harness-worker.js', { type: 'module' })
|
|
worker.postMessage({ type: 'initialize' })
|
|
const ready = await waitFor(worker, 'ready')
|
|
result.initialize = ready.occtVersion
|
|
worker.postMessage({ type: 'history', requestId: 'history-1', operation: 'cut' })
|
|
const history = await waitFor(worker, 'history')
|
|
result.history = { provider: history.provider, recordCount: history.recordCount }
|
|
const stressIterations = 1000
|
|
const browserBefore = await measureBrowserMemory()
|
|
worker.postMessage({ type: 'stress', requestId: 'stress-1', iterations: stressIterations })
|
|
const stress = await waitFor(worker, 'stress')
|
|
const browserAfter = await measureBrowserMemory()
|
|
const browserRetainedBytes = browserBefore.bytes === null || browserAfter.bytes === null ? null : browserAfter.bytes - browserBefore.bytes
|
|
result.memory = {
|
|
iterations: stress.iterations,
|
|
budgets: memoryBudgets,
|
|
wasm: stress.wasm,
|
|
browser: { before: browserBefore, after: browserAfter, retainedBytes: browserRetainedBytes },
|
|
gpu: stress.gpu,
|
|
ownership: stress.ownership
|
|
}
|
|
result.memory.withinBudget =
|
|
stress.wasm.peakHeapBytes <= memoryBudgets.wasmPeakBytes &&
|
|
stress.wasm.retainedBytes <= memoryBudgets.wasmRetainedBytes &&
|
|
browserRetainedBytes !== null && browserRetainedBytes <= memoryBudgets.browserRetainedBytes &&
|
|
stress.gpu.live <= memoryBudgets.gpuLiveResources &&
|
|
stress.ownership.live === 0 && stress.ownership.created === stress.ownership.released
|
|
worker.postMessage({ type: 'cancel', requestId: 'cancel-1' })
|
|
const cancelled = await waitFor(worker, 'cancelled')
|
|
result.abort = cancelled.requestId === 'cancel-1' ? 'recovered' : 'failed'
|
|
worker.terminate()
|
|
worker = new Worker('/native/occt-history/browser-harness-worker.js', { type: 'module' })
|
|
worker.postMessage({ type: 'initialize' })
|
|
const restarted = await waitFor(worker, 'ready')
|
|
result.crashRecovery = restarted.occtVersion === ready.occtVersion ? 'recovered' : 'failed'
|
|
worker.terminate()
|
|
if (!result.crossOriginIsolated || result.opfs?.status !== 'pass' || result.opfs.persistence !== 'opfs' || result.abort !== 'recovered' || result.crashRecovery !== 'recovered' || result.memory.iterations !== stressIterations || result.memory.withinBudget !== true) throw new Error('Browser isolation, OPFS, Worker recovery or memory budget contract failed.')
|
|
return result
|
|
}
|
|
|
|
try {
|
|
const result = await run()
|
|
output.textContent = `BROWSER_OCCT_RESULT=${JSON.stringify(result)}`
|
|
await fetch('/__occt-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(result) })
|
|
} catch (error) {
|
|
const result = { status: 'failed', error: error instanceof Error ? error.message : String(error) }
|
|
output.textContent = `BROWSER_OCCT_RESULT=${JSON.stringify(result)}`
|
|
await fetch('/__occt-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(result) })
|
|
throw error
|
|
}
|
|
</script>
|