oxedyne/daimond/dev/probe_opfsstamp.mjs
2.8 KiB, 1 run
created by r2519314175:59, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // probe_opfsstamp.mjs — does an OPFS file keep the same `lastModified` when nobody |
| 2 | // writes it? |
| 3 | // |
| 4 | // The live view decides a source has changed by asking every watched file for |
| 5 | // `lastModified` and `size` and comparing the answer with last time's. That is the |
| 6 | // only mechanism there is — the File System Access API has no change events — so if |
| 7 | // a browser hands back a fresh time on every `getFile()`, the watch loop rebuilds |
| 8 | // once a second with nothing edited, which is exactly what the author reported and |
| 9 | // what headless Chromium here does NOT do. So ask each engine, with no app in the |
| 10 | // way: one file, written once, asked five times over five seconds. |
| 11 | // |
| 12 | // It asserts nothing and is not a verifier. |
| 13 | // |
| 14 | // eval "$(bash dev/world.sh 5 --env)" |
| 15 | // node dev/probe_opfsstamp.mjs # every engine that is installed |
| 16 | // node dev/probe_opfsstamp.mjs chromium webkit |
| 17 | import os from 'node:os'; |
| 18 | import path from 'node:path'; |
| 19 | import { pathToFileURL } from 'node:url'; |
| 20 | |
| 21 | const PW = process.env.DAIMOND_PW |
| 22 | || path.join(os.homedir(), '.red-pw/node_modules/playwright-core/index.mjs'); |
| 23 | const pw = await import(pathToFileURL(PW).href); |
| 24 | const APP = process.env.DAIMOND_APP || 'http://localhost:8777'; |
| 25 | |
| 26 | const want = process.argv.slice(2).filter(a => !a.startsWith('-')); |
| 27 | const engines = (want.length ? want : ['chromium', 'firefox', 'webkit']); |
| 28 | |
| 29 | const SCRIPT = async () => { |
| 30 | const out = { steps: [] }; |
| 31 | try { |
| 32 | const root = await navigator.storage.getDirectory(); |
| 33 | const h = await root.getFileHandle('stampprobe.txt', { create: true }); |
| 34 | const w = await h.createWritable(); |
| 35 | await w.write('one'); |
| 36 | await w.close(); |
| 37 | for (let i = 0; i < 5; i++) { |
| 38 | const f = await h.getFile(); |
| 39 | out.steps.push({ at: i, lastModified: f.lastModified, size: f.size }); |
| 40 | await new Promise(r => setTimeout(r, 1000)); |
| 41 | } |
| 42 | } catch (e) { |
| 43 | out.error = String(e && e.message ? e.message : e); |
| 44 | } |
| 45 | return out; |
| 46 | }; |
| 47 | |
| 48 | for (const name of engines) { |
| 49 | const type = pw[name]; |
| 50 | if (!type) { console.log(`${name}: not in playwright-core`); continue; } |
| 51 | let b; |
| 52 | try { |
| 53 | b = await type.launch({ headless: true }); |
| 54 | } catch (e) { |
| 55 | console.log(`${name}: will not launch — ${String(e.message).split('\n')[0]}`); |
| 56 | continue; |
| 57 | } |
| 58 | try { |
| 59 | const p = await b.newPage(); |
| 60 | await p.goto(APP + '/index.html', { waitUntil: 'domcontentloaded' }); |
| 61 | const r = await p.evaluate(SCRIPT); |
| 62 | if (r.error) { console.log(`${name}: ${r.error}`); } |
| 63 | else { |
| 64 | const st = r.steps.map(s => s.lastModified); |
| 65 | const same = st.every(v => v === st[0]); |
| 66 | console.log(`${name}: lastModified ${same ? 'STABLE' : 'MOVES'} — ${st.join(', ')}`); |
| 67 | if (!same) console.log(' → a watch loop on this engine rebuilds on every poll'); |
| 68 | } |
| 69 | const ua = await p.evaluate(() => navigator.userAgent); |
| 70 | console.log(` ${ua}`); |
| 71 | } catch (e) { |
| 72 | console.log(`${name}: ${String(e.message).split('\n')[0]}`); |
| 73 | } |
| 74 | await b.close(); |
| 75 | } |