DOM & Automation — Full Catalog
Interact safely with the remote page: type, click, select, submit, wait, highlight, style, read fragments, run small page-side snippets.
Everything runs inside the controlled tab (content script) — no SOP bypass, no remote server. v7.10.1
tabKey (or tabId).
LAN/localhost: see the LAN page (double barrier + allow-list).
2) Minimal Request Helper (canonical)
Place a single helper in your layout. It returns { ok, data } or { ok:false, error:{ code, message } }.
<script>
const ADMIN_ORIGIN='admin', EXT_ORIGIN='extension';
let _seq=0, _waiters=new Map();
addEventListener('message', (e) => {
const d=e.data; if(!d||typeof d!=='object') return;
if(d.origin!==EXT_ORIGIN) return;
if(d.requestId && _waiters.has(d.requestId)) { _waiters.get(d.requestId)(d); _waiters.delete(d.requestId); }
});
function ask(action, args={}, timeoutMs=12000){
const requestId='r'+(++_seq);
postMessage({ origin:ADMIN_ORIGIN, type:'RTO_REQUEST', requestId, action, args }, '*');
return new Promise((resolve,reject)=>{
const t=setTimeout(()=>{ _waiters.delete(requestId); reject(Object.assign(new Error('timeout'),{ code:'TIMEOUT' })); }, timeoutMs);
_waiters.set(requestId, (resp)=>{ clearTimeout(t); resolve(resp); });
});
}
</script>
3) Safety Model (important)
- No SOP bypass: the content script acts within the target page. Cross-origin iframes remain inaccessible.
- Mandatory allow-list (deny-by-default): actions only work on hosts added by the user. On first attempt, an embedded banner proposes to add the domain, then replays the action with the same
focus. - Refused schemes:
http://,file://,javascript:. LAN/localhost targets are disabled by default and require double opt-in (flags + allow-list). See LAN. - Sensitive-field guard: read/write is blocked on passwords and sensitive fields →
ok:falsewitherror.code:"FORBIDDEN". - Faithful events: typing and value updates trigger
input/changeto mimic real user behavior. - Control banner: a thin orange banner (can minimize into a draggable chip) shows only in the controlled tab.
4) Action Catalog (v7.10.1)
Call ask('<action>', { ...args, tabKey? }) directly — no domAction envelope required.
| Action | Main args | Effect | Return |
|---|---|---|---|
domType |
{ selector, value, delayMs? } |
Types character by character (optional), then fires input/change. |
{ ok:true } |
domSetValue |
{ selector, value, clear? } |
Sets value immediately (no delay), fires input/change. |
{ ok:true } or { ok:false, error:{ code:'FORBIDDEN' }} |
domClick |
{ selector } |
Scrolls into view and clicks a visible element. | { ok:true } |
focusElement |
{ selector } |
Sets focus (tries to avoid scroll jumps). | { ok:true } |
submitForm |
{ selector } (a <form>) |
Uses requestSubmit() if available, otherwise a safe fallback. |
{ ok:true } |
selectSetValue |
{ selector, value } |
Changes a <select> and triggers input/change. |
{ ok:true } |
waitFor |
{ selector, timeoutMs } |
Resolves when the element appears, otherwise TIMEOUT. | { ok:true } or { ok:false, error:{ code:'TIMEOUT' }} |
domSetStyle |
{ selector, style:{ ... } } |
Applies a safe subset of CSS properties (with !important). |
{ ok:true } |
highlight |
{ selector, color?, ms? } |
Temporary halo; auto-restore after ms. |
{ ok:true } |
getHtml |
{ selector, property } where property ∈ outerHTML|innerHTML|textContent|innerText|value |
Reads a safe fragment (with sensitive-field guard). | { ok:true, data:{ value:<string> } } or an error |
{ type:'domAction', action:'...' }, see the “Legacy → current” table below.
| Legacy (≤ v7.9.x) | v7.10.1 (recommended) |
|---|---|
{ type:'domAction', action:'type', payload:{...} } | ask('domType', {...}) |
{ type:'domAction', action:'setValue', payload:{...} } | ask('domSetValue', {...}) |
{ type:'domAction', action:'click', payload:{...} } | ask('domClick', {...}) |
{ type:'domAction', action:'submit', payload:{...} } | ask('submitForm', {...}) |
{ type:'domAction', action:'selectSetValue', payload:{...} } | ask('selectSetValue', {...}) |
{ type:'domAction', action:'waitFor', payload:{...} } | ask('waitFor', {...}) |
{ type:'domAction', action:'domSetStyle', payload:{...} } | ask('domSetStyle', {...}) |
{ type:'domAction', action:'highlight', payload:{...} } | ask('highlight', {...}) |
{ type:'domAction', action:'getHtml', payload:{...} } | ask('getHtml', {...}) |
5) Safe Style & Highlight
5.1 Apply a safe style
await ask('domSetStyle', {
selector:'#email',
style:{ outline:'3px solid #1e88e5', backgroundColor:'rgba(30,136,229,0.10)' },
tabKey:'sales'
});
Allowed families: outline*, background*, border*, boxShadow.
5.2 Temporary highlight
await ask('highlight', { selector:'#email', color:'#FFCC00', ms:1400, tabKey:'sales' });
6) Typing & Value Updates
Useful for fields with live validation/search.
await ask('domType', {
selector:'#email', value:'dev@example.com', delayMs:20, tabKey:'sales'
});
Faster; still triggers input/change.
await ask('domSetValue', {
selector:'#search', value:'hello world', clear:true, tabKey:'sales'
});
<input type="file">.7) Click & Submit
Scrolls into view, then clicks.
await ask('domClick', { selector:'button[type=submit]', tabKey:'sales' });
If the button is disabled, fill required fields first.
Works even without a visible button.
await ask('submitForm', { selector:'form#login', tabKey:'sales' });
8) Dropdowns
await ask('selectSetValue', { selector:'select#country', value:'BE', tabKey:'sales' });
If you need to select by label:
await ask('runJs', { code:`(function(){
const s=document.querySelector('select#country'); if(!s) return false;
const opt=[...s.options].find(o=>o.textContent.trim()==='Belgium'); if(!opt) return false;
s.value=opt.value; s.dispatchEvent(new Event('input',{bubbles:true}));
s.dispatchEvent(new Event('change',{bubbles:true}));
return true;
})()`, tabKey:'sales' });
9) Wait for elements (SPA-friendly)
// Wait for the form
await ask('waitFor', { selector:'#email', timeoutMs:20000, tabKey:'sales' });
// Fill & submit
await ask('domType', { selector:'#email', value:'dev@example.com', tabKey:'sales' });
await ask('domType', { selector:'#note', value:'hello world', tabKey:'sales' });
await ask('domClick', { selector:'button[type=submit]', tabKey:'sales' });
{ ok:false, error:{ code:'TIMEOUT' }}.10) Reading with getHtml
10.1 Read text
const r = await ask('getHtml', { selector:'#lorem-block', property:'textContent', tabKey:'sales' });
console.log('Text (first 120):', (r.ok ? r.data.value : '').trim().slice(0,120)+'…');
10.2 Read a value (with sensitive guard)
const r = await ask('getHtml', { selector:'#search', property:'value', tabKey:'sales' });
if (r.ok) console.log('Value:', r.data.value);
else console.warn('getHtml failed:', r.error?.code);
On sensitive fields (password/CC/IBAN…), the response is ok:false, error.code:'FORBIDDEN'.
11) runJs (small page-side snippets)
Keep your snippets small and self-contained. For sensitive operations, prefer named actions.
const { ok, data, error } = await ask('runJs', { code:'document.title', tabKey:'sales' });
console.log('Title:', ok ? data : error?.message);
await ask('runJs', { code:`(function(){
const el = document.querySelector('#status'); if (!el) return false;
el.textContent = 'Connected ✅'; el.classList.add('ok'); el.style.outline = '2px solid #0d6efd'; return true;
})()`, tabKey:'sales' });
await ask('runJs', { code:`(function(){
const id='rto-banner'; let el=document.getElementById(id);
if(!el){ el=document.createElement('div'); el.id=id;
el.textContent='Controlled by Remote Tab Opener';
el.style.cssText='position:fixed;bottom:0;left:0;right:0;padding:8px;background:#0d6efd;color:#fff;z-index:2147483647;font:14px system-ui';
document.body.appendChild(el);
} return true;
})()`, tabKey:'sales' });
12) Error Mapping
Always check ok. On failure, use error.code (and message) to guide the user.
error.code |
Actions | Probable cause | Solution |
|---|---|---|---|
DOMAIN_NOT_ALLOWED |
open/navigate/DOM | Domain not on the allow-list. | App-side banner + extension popup. Replay after adding. |
INVALID_URL |
openTab/navigate | Non-HTTPS/forbidden/LAN URL. | Absolute https:// URL. See LAN. |
NO_CONTROLLED_TAB |
getCurrentUrl/DOM/runJs | No controlled tab. | Call openTab or navigate first. |
ELEMENT_NOT_FOUND |
domType/domSetValue/domClick/… | Wrong selector or DOM not ready. | waitFor, review selector, account for SPA rendering. |
TIMEOUT |
waitFor | Element never appeared. | Increase timeoutMs, retries, other signal. |
FORBIDDEN |
domType/domSetValue/getHtml(value) | Protected sensitive field. | Avoid reading/writing secrets; adapt the flow. |
EXECUTION_ERROR |
runJs | Exception (null/CSP/syntax). | Short snippets, IIFE, null-guards, no remote imports. |
13) Patterns & Mini-Recipes
await ask('navigate', { url:'https://app.example.com/login', tabKey:'auth' });
// if already signed in, redirect to /dashboard is fine
await ask('waitFor', { selector:'#email', timeoutMs:20000, tabKey:'auth' });
await ask('domSetValue', { selector:'#email', value:'user@example.com', tabKey:'auth' });
await ask('domSetValue', { selector:'#note', value:'hello world', tabKey:'auth' });
await ask('domClick', { selector:'button[type=submit]', tabKey:'auth' });
await ask('waitFor', { selector:'.badge.ready', timeoutMs:30000, tabKey:'auth' });
Many UIs hide the real input; clicking the label is often simpler. Otherwise:
await ask('runJs', { code:`(function(){
const box = document.querySelector('#tos'); if(!box) return false;
box.checked = true; box.dispatchEvent(new Event('change',{bubbles:true})); return true;
})()`, tabKey:'auth' });
await ask('runJs', { code:`(function(){
const el = document.querySelector('#important'); if(!el) return false;
el.scrollIntoView({behavior:'smooth', block:'center'});
el.style.transition='box-shadow .2s ease';
el.style.boxShadow='0 0 0 4px rgba(13,110,253,.35)';
setTimeout(()=>{ el.style.boxShadow=''; }, 1200);
return true;
})()`, tabKey:'auth' });
const { ok, data } = await ask('runJs', { code:`(function(){
const n=document.querySelector('#cart-count'); return { result: n?parseInt(n.textContent,10):0 };
})()`, tabKey:'auth' });
if (!ok || data.result < 1) throw new Error('Cart is empty');
14) Pitfalls & Edge Cases
- Cross-origin iframes: inaccessible by design. Navigate directly to the target origin.
- Hidden/disabled elements: a click will fail. Meet prerequisites first (required fields, accordions…).
- Fragile selectors: prefer stable
#idor[data-testid]. - Slow apps: raise
timeoutMs(20–30s) and usewaitFor. - File inputs: no programmatic filling.
Next Pages
| Page | Description |
|---|---|
| Start | Overview, how it works, setup, quick start, and links to all sections. |
| Detect the Extension | Detection contract (ping/pong), install prompts, and graceful fallbacks when the extension isn’t present. |
| Allow-list & Permissions | Deny-by-default model, domain allow-list via popup, and suggested UX to guide users to enable hosts. |
| LAN / Localhost | How to safely enable intranet/localhost (flags + allow-list). |
| Remote Tab Control | Open/navigate/focus the controlled tab, read URL/title, select an existing tab, lifecycle & restore patterns. |
| DOM & Automation | Action catalog (type, setValue, click, select, submit, waitFor) and safe interaction tips. |
| Recipes & Flows | Copy-paste flows: resilient login, dashboard checks, table waits, filters, playlists, and allow-list banners. |
| Events | Event stream reference, subscription helpers, live logger widget, and basic metrics/durations collection. |
| Diagnostics | Inline console, error cookbook, QA self-check, and performance tips for troubleshooting flows quickly. |
| Full API Reference | Canonical message envelopes, request/response schemas, error codes, and return shapes for every action. |
| Compatibility | Supported browsers/versions, permissions nuances, CSP/iframe notes, and known limitations or workarounds. |
| Advanced Patterns | Idempotent flows, retries/backoff, state restore after reload, playlist strategies, and robust selectors. |
| Favorites | Star and group URLs, quick actions (open/focus/navigate), export/import sets, and playlist runs from favorites. |
| Real-World Flows (E2E) | Run end-to-end flows from your admin page: helper setup, modern message contract, copy-paste journeys (login, multi-tab, extract), lightweight assertions, best practices, and troubleshooting. |
Need help writing safe flows?