let lastList = []; function safeText(el) { let t = (el.innerText || el.textContent || '').trim(); if (t.length > 80) t = t.slice(0, 80) + '…'; return t; } function describe(el) { let sel = el.id ? '#' + el.id : el.tagName.toLowerCase(); if (!el.id && el.className && typeof el.className === 'string') { sel += '.' + el.className.trim().split(/\s+/).slice(0, 2).join('.'); } const label = el.getAttribute('aria-label') || el.getAttribute('title') || el.getAttribute('name') || ''; return { tag: el.tagName.toLowerCase(), selector: sel, text: safeText(el), label, type: el.getAttribute('type') || '', disabled: !!el.disabled, }; } export function listClickables() { const els = Array.from( document.querySelectorAll( 'button, a[href], input[type=submit], input[type=button], [role=button], [onclick]' ) ); lastList = els.filter((e) => { const r = e.getBoundingClientRect(); return r.width > 0 && r.height > 0; }); return lastList.map((el, i) => { const d = describe(el); d.index = i; return d; }); } export function clickElement(params) { let el; if (params.selector) el = document.querySelector(params.selector); else if (typeof params.index === 'number') el = lastList[params.index]; if (!el) return { ok: false, error: 'element not found' }; if (el.disabled) return { ok: false, error: 'element is disabled' }; try { el.scrollIntoView({ block: 'center' }); el.click(); return { ok: true }; } catch (e) { return { ok: false, error: String(e) }; } } export function typeText(params) { let el; if (params.selector) el = document.querySelector(params.selector); else if (typeof params.index === 'number') el = lastList[params.index]; if (!el) return { ok: false, error: 'element not found' }; const proto = Object.getPrototypeOf(el); const setter = Object.getOwnPropertyDescriptor(proto, 'value'); try { if (setter && setter.set) setter.set.call(el, params.text || ''); else el.value = params.text || ''; } catch { el.value = params.text || ''; } el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); return { ok: true }; } export function evalExpr(params) { try { const r = (0, eval)(params.expr); return { ok: true, value: serialize(r) }; } catch (e) { return { ok: false, error: String(e) }; } } export function currentUrl() { return { url: location.href, title: document.title }; } function serialize(v) { if (v === undefined) return 'undefined'; try { return JSON.parse(JSON.stringify(v)); } catch { return String(v); } } export function dispatch(msg) { const p = msg.params || {}; switch (msg.action) { case 'list_clickables': return listClickables(); case 'click': return clickElement(p); case 'eval': return evalExpr(p); case 'current_url': return currentUrl(); case 'type': return typeText(p); default: return { ok: false, error: 'unknown action: ' + msg.action }; } }