Allow-list & Permissions
The extension acts only on domains explicitly allowed by the user in its popup. This page explains the mental model and how to guide users to enable hosts.
Privacy-first • No cookies • No background tracking • Firefox ✓ (Chrome MV3 soon) • Canonical envelopes v7.10.1
Mental model
- Per-host control: actions run only on hosts present in the Allow-list (e.g.,
app.example.com). - Wildcards: patterns like
*.example.comallow all subdomains ofexample.com. - Granular & local: the list is stored locally by the extension and never uploaded.
2) Default policy & banner (v7.10+)
By default (deny-by-default), no remote domains are allowed when the list is empty. When you call open/navigate to a non-listed domain, the extension shows a banner:
- “Domain not allowed — Add to the list?” → Add writes the host into the allow-list (wildcards supported, e.g.
*.example.com). - The pending action (
open/navigate) is automatically resumed after “Add”, preserving the requestedfocus(on/off).
Allow-list entries may be provided as HTTPS origins (e.g., https://example.org) or as hosts / wildcards (e.g., *.example.*). Origins are normalized to HTTPS by default.
- Use
https://URLs.javascript:is blocked.file://is not supported for remote control. - LAN/private hosts (e.g.,
localhost,192.168.x.x) require explicit allow-listing and may be restricted by the browser.
3) When you’ll need the Allow-list
- Whenever you call
open,navigate,focus*,dom*, orrunJson a target domain. - Reading remote URL/title also requires a controlled tab on an allowed domain.
4) Detect & Show a Banner (copy-paste)
Pattern: attempt a harmless action (e.g., navigate) → if the extension replies with ok:false and error.code:"DOMAIN_NOT_ALLOWED", show an app-side banner that explains how to add the domain, then offer “Try again”.
4.1 Minimal request helper (canonical v7.10.1)
<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.type==='RTO_RESPONSE' && d.requestId && _waiters.has(d.requestId)) {
const fn=_waiters.get(d.requestId); _waiters.delete(d.requestId); fn(d);
}
});
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); });
});
}
// Optional: retry with backoff for transient hiccups (e.g., first-run content script warmup)
async function askBackoff(action, args={}, tries=3, base=300){
let n=0;
while(true){
try{
const r = await ask(action, args, 12000);
if(!r.ok) throw Object.assign(new Error(r.error?.message||'error'), r.error||{});
return r;
}catch(e){
if(++n >= tries) throw e;
await new Promise(r=>setTimeout(r, base*Math.pow(2,n-1)));
}
}
}
</script>
4.2 Test a domain and raise the banner
We purposely “probe” with navigate (safe and idempotent). If it fails with DOMAIN_NOT_ALLOWED, we reveal the banner. Les anciennes versions pouvaient simplement expirer — on gère aussi ce cas.
<div id="allow-banner" class="alert alert-warning d-none">
<strong>Allow-list required:</strong>
Open the extension popup and add <code class="domain">example.com</code>.
<button class="btn btn-sm btn-outline-primary" id="allow-try">Try again</button>
</div>
<script>
async function ensureAllowed(url){
const host = new URL(url).host;
try{
const r = await ask('navigate', { url, focus:false }, 12000);
if (r.ok) return true;
if (r.error?.code === 'DOMAIN_NOT_ALLOWED'){
const box = document.getElementById('allow-banner');
box.classList.remove('d-none');
box.querySelector('.domain').textContent = host;
document.getElementById('allow-try').onclick = () => location.reload();
return false;
}
// Other structured errors: surface and stop
throw Object.assign(new Error(r.error?.message||'error'), r.error||{});
}catch(e){
// TIMEOUT or older builds that didn't answer → show banner anyway
const box = document.getElementById('allow-banner');
if (box){
box.classList.remove('d-none');
box.querySelector('.domain').textContent = host;
document.getElementById('allow-try').onclick = () => location.reload();
}
return false;
}
}
ensureAllowed('https://example.com/');
</script>
The extension also displays its own “Domain not allowed — Add to the list?” banner and automatically resumes the pending action after the user confirms. This app-side banner is a complementary UX, not a requirement.
4.3 Non-blocking “preflight” check
Instead of navigating, you can preflight with a dry getUrl call. It’s less explicit but avoids navigation during checks.
async function preflightAllow(url){
try{
const r = await ask('getUrl', {}, 4000);
const cur = r.ok ? (r.data?.url||'') : '';
return cur.startsWith(new URL(url).origin);
}catch(_){ return false; }
}
5) Friendly UI Patterns
- Inline banner with the exact domain: “Add
app.example.comto the Allow-list”. - Retry button that simply reloads or re-calls the action.
- Explain where the popup is: “Click the extension icon (top-right), then ‘Allow-list’ → Add”.
- Progressive enhancement: hide “Remote” buttons until the extension responds to a quick
ask('getUrl')orask('getTitle').
localStorage.rtoAllowed["example.com"]=true after a successful call to avoid re-prompting every page view.6) Multi-domain Projects
If your flow touches several hosts, guide users to allow them all once. Use a simple checklist:
6.1 Checklist component
<ul id="rto-allow-checklist" class="list-group"></ul>
<script>
// Requires the canonical ask() and ensureAllowed() above.
const NEED = ['https://app.example.com','https://docs.example.com','https://admin.example.com'];
(async()=>{
const ul = document.getElementById('rto-allow-checklist');
for(const base of NEED){
const li=document.createElement('li'); li.className='list-group-item d-flex justify-content-between align-items-center';
const host=new URL(base).host; li.innerHTML = '<span>'+host+'</span><span class="badge bg-secondary">checking…</span>';
ul.appendChild(li);
const ok = await ensureAllowed(base);
const badge = li.querySelector('.badge');
badge.className = 'badge '+(ok?'bg-success':'bg-warning text-dark');
badge.textContent = ok?'allowed':'needed';
}
})();
</script>
Sub-domains are distinct: adding example.com doesn’t always imply app.example.com. Prefer listing exact hosts (or use a wildcard like *.example.com if appropriate).
7) FAQ & Troubleshooting
| Question | Answer |
|---|---|
| Can my web app add a domain to the Allow-list automatically? | No. For privacy and safety, only the user can modify the Allow-list via the extension popup. |
We allowed example.com but actions on app.example.com still fail. |
Allow the exact host you target. Many setups treat sub-domains separately unless you add a wildcard. |
| What code indicates this problem? | ok:false with error.code:"DOMAIN_NOT_ALLOWED" in the canonical response. Show the banner and guide the user. |
| Can I test without real navigation? | Yes, use a preflight like ask('getUrl') (see 4.3), or a no-op ask('runJs', { code:'true' }) once the tab is on the right origin. |
| What if the user closes the popup without adding the domain? | Keep the banner visible and provide “Try again” after they reopen the popup and add it. |
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. |
| 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?