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

scripts/web-probe/ime.mjs (3.6K)

 1 // ime.mjs <site-dir> [<out-dir>]: input-method composition through the
 2 // browser shell, in the demo's text box, driven by Chromium's own IME path
 3 // (CDP Input.imeSetComposition / Input.insertText — the events a real IME
 4 // raises in the focused textarea). Each result is read back by copying the
 5 // box's text to the system clipboard. With <out-dir>, the frame mid-
 6 // composition is saved there (ime-composing.rgba).
 7 //   1. a composition shows in the box, and the keyboard sink sits at its caret;
 8 //   2. the commit replaces it with the committed text;
 9 //   3. a composition cancelled leaves the box as it was;
10 //   4. text with no composition (an emoji panel) is typed;
11 //   5. plain keys still type.
12 import fs from 'fs';
13 import { open } from './browser.mjs';
14 
15 const [root, out] = process.argv.slice(2);
16 if (!root) { console.error('usage: ime.mjs <site-dir> [<out-dir>]'); process.exit(2); }
17 let ok = true;
18 const check = (what, pass, detail) => {
19   ok &&= pass;
20   console.log(`${pass ? 'ok  ' : 'FAIL'} ${what}${detail ? ': ' + detail : ''}`);
21 };
22 const { page, logs, close } = await open(root, 'demo.html', 1280, 800);
23 await page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
24 const cdp = await page.context().newCDPSession(page);
25 const m = page.mouse, k = page.keyboard, wait = ms => page.waitForTimeout(ms);
26 const chord = async key => { await k.down('Control'); await k.press(key); await k.up('Control'); await wait(300); };
27 const boxText = async () => {
28   await chord('a'); await chord('c');
29   const t = await page.evaluate(() => navigator.clipboard.readText());
30   await k.press('End'); await wait(200);
31   return t;
32 };
33 const compose = async (text, at) => {
34   await cdp.send('Input.imeSetComposition', { text, selectionStart: at, selectionEnd: at });
35   await wait(400);
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 m.move(640, 168); await m.down(); await m.up(); await wait(300);
42   await k.type('ab'); await k.press('ArrowLeft'); await wait(300);
43 
44   await compose('にほ', 1);
45   if (out) {
46     fs.mkdirSync(out, { recursive: true });
47     const b64 = await page.evaluate(() => window.capture());
48     fs.writeFileSync(`${out}/ime-composing.rgba`, Buffer.from(b64, 'base64'));
49   }
50   const at = await page.evaluate(() => {
51     const t = document.activeElement;
52     return { tag: t.tagName, left: parseFloat(t.style.left), top: parseFloat(t.style.top), value: t.value };
53   });
54   check('the keyboard sink has the focus and holds the composition', at.tag === 'TEXTAREA' && at.value === 'にほ', JSON.stringify(at));
55   // The box (the demo's, at y 155-181) and its caret after "aに".
56   check('the sink sits at the caret', at.top > 150 && at.top < 185 && at.left > 40 && at.left < 120, `left ${at.left}, top ${at.top}`);
57 
58   await cdp.send('Input.insertText', { text: '日本' }); await wait(400);
59   let t = await boxText();
60   check('the commit replaces the composition', t === 'a日本b', JSON.stringify(t));
61 
62   await compose('か', 1);
63   await compose('', 0);
64   t = await boxText();
65   check('a cancelled composition leaves the box as it was', t === 'a日本b', JSON.stringify(t));
66 
67   await cdp.send('Input.insertText', { text: '😀' }); await wait(400);
68   t = await boxText();
69   check('text with no composition is typed', t === 'a日本b😀', JSON.stringify(t));
70 
71   await k.type('xy'); await wait(300);
72   t = await boxText();
73   check('plain keys still type', t === 'a日本b😀xy', JSON.stringify(t));
74 } catch (e) { ok = false; logs.push('[ime] ' + String(e)); }
75 for (const l of logs) if (!l.includes('WebGPU is experimental')) console.log(l);
76 await close();
77 process.exit(ok ? 0 : 1);