git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

scripts/web-probe/clipboard.mjs (3.9K)

 1 // clipboard.mjs <site-dir>: the clipboard through the browser shell, in the
 2 // demo's text box (src/main.rs on demo_web). Four checks, each read back
 3 // from the system clipboard:
 4 //   1. copy — Ctrl+A, Ctrl+C — puts the box's text there;
 5 //   2. paste — Ctrl+V with other text there — takes it (copied back out);
 6 //   3. copy with the page's writeText refused: the `copy` event alone
 7 //      carries it (what an insecure context has);
 8 //   4. paste with the `paste` event swallowed but its default kept: the text
 9 //      the browser pastes into the keyboard sink is the clipboard's;
10 //   5. paste with the `paste` event swallowed and cancelled: the held key
11 //      goes through on its timer and pastes the page's own last copy or
12 //      paste (4's read-back copy of the box).
13 import { open } from './browser.mjs';
14 
15 const [root] = process.argv.slice(2);
16 if (!root) { console.error('usage: clipboard.mjs <site-dir>'); process.exit(2); }
17 let ok = true;
18 const check = (what, got, want) => {
19   const pass = got === want;
20   ok &&= pass;
21   console.log(`${pass ? 'ok  ' : 'FAIL'} ${what}: ${JSON.stringify(got)}${pass ? '' : ` (want ${JSON.stringify(want)})`}`);
22 };
23 
24 async function session(body) {
25   const { page, logs, close } = await open(root, 'demo.html', 1280, 800);
26   await page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
27   const m = page.mouse, k = page.keyboard, wait = ms => page.waitForTimeout(ms);
28   const t = {
29     page, wait,
30     read: () => page.evaluate(() => navigator.clipboard.readText()),
31     write: s => page.evaluate(s => (window.realWrite || (t => navigator.clipboard.writeText(t)))(s), s),
32     chord: async key => { await k.down('Control'); await k.press(key); await k.up('Control'); await wait(400); },
33     focusBox: async () => { await m.move(640, 168); await m.down(); await m.up(); await wait(300); },
34     type: async s => { await k.type(s); await wait(300); },
35     key: async s => { await k.press(s); await wait(100); },
36   };
37   try {
38     await m.move(1279, 799);
39     await page.waitForFunction(() => window.demoReady === true, null, { timeout: 120000 });
40     await wait(3000);
41     await body(t);
42   } catch (e) { ok = false; logs.push('[clipboard] ' + String(e)); }
43   for (const l of logs) if (!l.includes('WebGPU is experimental')) console.log(l);
44   await close();
45 }
46 
47 await session(async t => {
48   await t.write('before');
49   await t.focusBox(); await t.type('hello web');
50   await t.chord('a'); await t.chord('c');
51   check('copy', await t.read(), 'hello web');
52   await t.write('from the system');
53   await t.chord('a'); await t.chord('v');
54   await t.chord('a'); await t.chord('c');
55   check('paste', await t.read(), 'from the system');
56 });
57 
58 await session(async t => {
59   await t.write('before');
60   await t.page.evaluate(() => {
61     const real = Clipboard.prototype.writeText;
62     window.realWrite = s => real.call(navigator.clipboard, s);
63     Clipboard.prototype.writeText = () => Promise.reject(new Error('refused'));
64   });
65   await t.focusBox(); await t.type('via the event');
66   await t.chord('a'); await t.chord('c');
67   check('copy, the copy event alone', await t.read(), 'via the event');
68   // Ahead of the shell's listener: swallow the event, and with
69   // `cancelPaste` set cancel its default too.
70   await t.page.evaluate(() => window.addEventListener('paste', e => {
71     e.stopImmediatePropagation();
72     if (window.cancelPaste) e.preventDefault();
73   }, true));
74   await t.write(' and the system');
75   await t.focusBox(); await t.key('End'); await t.chord('v');
76   await t.chord('a'); await t.chord('c');
77   check('paste, the event swallowed', await t.read(), 'via the event and the system');
78   await t.page.evaluate(() => { window.cancelPaste = true; });
79   await t.write('system text the page never sees');
80   await t.focusBox(); await t.key('End'); await t.chord('v');
81   await t.chord('a'); await t.chord('c');
82   check('paste, no paste event at all', await t.read(), 'via the event and the systemvia the event and the system');
83 });
84 
85 process.exit(ok ? 0 : 1);