Favorites & Quick Actions
Speed up QA runs and daily tasks: star pages, pin hosts, group by tags, and run playlists.
Pure client-side · canonical ask(action, args) · respects the allow-list
v7.10.0
Prerequisites — why might “nothing happen”?
This page demonstrates integrations with the Remote Tab Opener extension. Actions only do something if:
This page demonstrates integrations with the Remote Tab Opener extension. Actions only do something if:
- the extension responds to your
RTO_REQUEST/RTO_RESPONSEhelper; - a remote tab is under control and
getUrl/getTitlereturn anhttps://…URL; - the target domain is on the allow-list (otherwise you’ll get
DOMAIN_NOT_ALLOWEDand a guidance banner).
2) Minimal request helper (canonical)
One helper across pages gives stable conventions (timeouts, requestId pairing). Includes askBackoff for retries with exponential backoff.
<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); });
});
}
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))); }
}
}
// Mark this tab as master (fire-and-forget)
try{ postMessage({ origin:ADMIN_ORIGIN, type:'RTO_REQUEST', requestId:'boot1', action:'setMasterTab', args:{} }, '*'); }catch(_){}
</script>
3) Data model (localStorage)
Favorites live in localStorage["rto.favorites"]. Pinned hosts live in localStorage["rto.pins"].
// localStorage["rto.favorites"]
[
{ "id":"fav_1", "title":"Example Dashboard", "url":"https://example.com/dashboard", "host":"example.com", "tags":["daily"], "addedAt":1730000000000 }
]
// localStorage["rto.pins"]
["example.com","docs.example.com"]
Tip: derive
host from the URL; use pins to keep key domains at the top and to filter playlists.
Allow-list required: open the extension popup and add
this-host.
Then click .
The extension’s built-in banner will also resume your pending action after consent.
4) Star current remote page
Shortcuts:
Ctrl/⌘+Shift+S star ·
Ctrl/⌘+Shift+P run playlist
Important: “Star” requires a controlled remote tab.
If it doesn’t exist or
getUrl returns empty (or non-HTTPS), nothing is added.
5) Favorites UI
| Title | Host | Tags | Actions |
|---|
Note: “Group by host” only changes rendering — add at least one favorite first (section 4).
Allow-list: if an action yields
DOMAIN_NOT_ALLOWED, the banner above appears.
Add the host in the extension popup, then press Try again.
6) One-click actions (helpers)
const Fav = {
openSilent: (url) => ask('open', { url, focus:false }),
go: (url) => ask('navigate', { url }),
goSilentOnce: async (url) => { await ask('suppressNextFocus'); return ask('navigate', { url }); },
focus: (url) => ask('focusByUrl', { url }),
assertOn: async (prefix) => {
const r = await ask('getUrl').catch(_=>null);
return !!r?.data?.url?.startsWith(prefix);
},
highlight: (selector) => ask('runJs', { code:`(function(){
const el=document.querySelector("${selector.replace(/"/g,'\\"')}"); if(!el) return false;
el.scrollIntoView({behavior:'smooth',block:'center'});
el.style.transition='box-shadow .2s'; el.style.boxShadow='0 0 0 4px rgba(13,110,253,.35)';
setTimeout(()=>el.style.boxShadow='', 1200); return true;
})()` })
};
7) Playlists from favorites
Run a sequence of favorites filtered by tag. Optionally limit to pinned hosts. Uses askBackoff (demo pattern).
async function runPlaylist(tag='daily', delayMs=700, pinsOnly=false){
const pins = new Set(_loadPins());
let favs = (_loadFavs()||[]).filter(f => !tag || (f.tags||[]).includes(tag));
if (pinsOnly) favs = favs.filter(f => pins.has(f.host));
if (!favs.length){ notify('No favorites match the filter.', 'warn'); return; }
for(const f of favs){
try{
await askBackoff('navigate', { url:f.url }, 4, 250);
await askBackoff('waitFor', { selector:'body', timeoutMs:15000 }, 3, 300);
console.log('Visited:', f.title);
}catch(e){ _handleActionError(e, f.url); }
await new Promise(r => setTimeout(r, delayMs));
}
notify('Playlist finished ✓','ok');
}
8) Import / Export — when and why
Why use it?
- Share a “QA” set with the team (Slack/Drive/Git);
- Back up your favorites before resetting your browser;
- Version a “daily/weekly” playlist in a repo.
Export: downloads favorites.json containing only your favorites (pinned hosts are not included — pin them again in your context).
Import: reads someone else’s or a backup JSON, merges it with your current favorites, and removes duplicates by URL.
9) Tips & keyboard shortcuts
- Before acting: call
getUrland assert a prefix, thenwaitFora concrete marker (e.g.,.ready). - Use Focus Master to return to this page after confirmations.
- Silent one-shot: click “Next navigate is silent” or use “Navigate (silent)” to avoid focus stealing.
- Tags: think
daily/qa/debugto group playlists. - Pins: keep critical hosts on top and filter runs with “Pinned hosts only”.
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 (domType, domSetValue, domClick, selectSetValue, submitForm, waitFor) and safe interaction tips. |
| Recipes & Flows | Copy-paste flows: login, dashboard checks, tables, filters, playlists, and allow-list banners. |
| Events | Event stream reference, subscription helpers, live logger widget, and basic metrics collection. |
| Diagnostics | Inline console, error cookbook, QA self-check, and performance tips. |
| 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. |
| Favorites | Star and group URLs, quick actions (open/focus/navigate), export/import sets, and playlist runs from favorites. |
| Real-World Flows (E2E) | End-to-end flows: helper setup, multi-tab, extract, assertions, and troubleshooting. |
Need help writing safe flows?