// cdp_pick_element_js - JS injected via CDP Runtime.evaluate. // Activates click-to-pick mode: hover highlights, click captures element selectors // and prints via console.log so caller (navegator_dashboard) reads via // Runtime.consoleAPICalled event. // // Inject as expression: see cdp_pick_element_js.md for the wrapping payload. // Idempotent: re-injecting cleans up previous overlays before reattaching. (function () { if (window.__fn_pick_active) { window.__fn_pick_cleanup && window.__fn_pick_cleanup(); } window.__fn_pick_active = true; const overlay = document.createElement('div'); overlay.id = '__fn_pick_overlay'; Object.assign(overlay.style, { position: 'fixed', pointerEvents: 'none', zIndex: '2147483647', border: '2px solid #f44', background: 'rgba(255,80,80,0.10)', boxSizing: 'border-box', transition: 'all 30ms linear', left: '0px', top: '0px', width: '0px', height: '0px', }); document.body.appendChild(overlay); function cssPath(el) { if (!(el instanceof Element)) return ''; const path = []; while (el && el.nodeType === Node.ELEMENT_NODE && el !== document.body) { let seg = el.tagName.toLowerCase(); if (el.id) { seg += '#' + CSS.escape(el.id); path.unshift(seg); break; } // Prefer class-based path when class is short and not utility-noise. if (el.className && typeof el.className === 'string') { const cls = el.className.trim().split(/\s+/).filter(c => c && c.length < 24 && !/^(active|hover|focus|selected|open|disabled|[0-9])/.test(c)); if (cls.length) seg += '.' + cls.map(CSS.escape).join('.'); } // Disambiguate by nth-of-type when siblings share tag. if (el.parentNode) { const siblings = [...el.parentNode.children].filter(c => c.tagName === el.tagName); if (siblings.length > 1) { const idx = siblings.indexOf(el) + 1; seg += `:nth-of-type(${idx})`; } } path.unshift(seg); el = el.parentNode; if (path.length > 6) break; } return path.join(' > '); } function xpath(el) { if (!el) return ''; if (el.id) return `//*[@id="${el.id}"]`; const parts = []; while (el && el.nodeType === Node.ELEMENT_NODE) { let ix = 0; for (const sib of el.parentNode ? el.parentNode.childNodes : []) { if (sib === el) { parts.unshift(`${el.tagName.toLowerCase()}[${ix + 1}]`); break; } if (sib.nodeType === 1 && sib.tagName === el.tagName) ix++; } el = el.parentNode; if (el === document.body) { parts.unshift('body'); break; } } return '/html/body/' + parts.join('/'); } function describe(el) { if (!el) return null; const r = el.getBoundingClientRect(); const attrs = {}; for (const a of el.attributes || []) attrs[a.name] = a.value; return { selector: cssPath(el), xpath: xpath(el), tag: el.tagName.toLowerCase(), text: (el.innerText || el.textContent || '').trim().slice(0, 200), attrs, rect: { x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height) }, }; } function onMove(e) { const el = e.target; if (!el || el === overlay) return; const r = el.getBoundingClientRect(); overlay.style.left = r.left + 'px'; overlay.style.top = r.top + 'px'; overlay.style.width = r.width + 'px'; overlay.style.height = r.height + 'px'; } function onClick(e) { e.preventDefault(); e.stopPropagation(); const info = describe(e.target); console.log('__fn_picked__', JSON.stringify(info)); cleanup(); } function onKey(e) { if (e.key === 'Escape') { console.log('__fn_picked__', JSON.stringify({ cancelled: true })); cleanup(); } } function cleanup() { window.__fn_pick_active = false; document.removeEventListener('mousemove', onMove, true); document.removeEventListener('click', onClick, true); document.removeEventListener('keydown', onKey, true); overlay.remove(); } window.__fn_pick_cleanup = cleanup; document.addEventListener('mousemove', onMove, true); document.addEventListener('click', onClick, true); document.addEventListener('keydown', onKey, true); return 'pick mode active'; })();