Messaging protocol
How the page and the extension communicate (you usually do not need to write this by hand).
Docs updated: 2026-03-24
Overview
RTO uses window.postMessage to exchange messages between your page and the extension. The helper RTO_form_api.js handles:
- Generating
requestIdvalues - Waiting for the matching
*Resultmessage - Timeouts (
TIMEOUT)
Recommendation
Use
RTO_form_api.js unless you are building your own wrapper.Modern allow-list flow
In 7.13.0, a page should ask through allowlistAddRequest.
The plugin then prompts on the matching master tab, and the master tab answers with either allowlistAddConfirm or allowlistAddReject.
| Message | Direction | Role |
|---|---|---|
allowlistAddRequest |
page → extension | Ask for a host to be added from a normal page. |
promptAllowlistAdd |
extension → master tab | Visible prompt sent to the master tab UI. |
allowlistAddConfirm |
master tab → extension | Approve the pending request. |
allowlistAddReject |
master tab → extension | Reject the pending request. |
allowlistAddRequestResult |
extension → page | Final success or failure returned to the requesting page. |
Important
Direct page-side
allowlistAdd is no longer the normal public flow.
It is intentionally blocked unless the plugin/internal UI performs the confirmation step.
Message envelope
Outgoing messages usually contain:
origin: a page-side marker (docs default:window.RTO_PAGE_ORIGIN)type: for example"detect","openTab", or"command"requestId: unique ID to match replies
The extension responds with type + "Result" (example: detectResult, commandResult).
Important
The
origin value is a protocol marker, not a security boundary. Security is enforced by the extension (allow-list + controlled tabs).
For raw code, always filter messages strictly (see examples).
Raw detect example
// Raw detect (without RTO_form_api.js)
(function(){
const requestId = "detect_" + Date.now() + "_" + Math.random().toString(16).slice(2);
const pageOrigin = window.RTO_PAGE_ORIGIN || "rto-docs";
let t;
function done(){
if(t) clearTimeout(t);
window.removeEventListener("message", onMsg);
}
function onMsg(ev){
const d = ev.data;
if(ev.source !== window) return; // same window only
if(!d || typeof d !== "object") return;
if(d.origin !== pageOrigin) return; // protocol marker must match
if(d.requestId !== requestId) return;
if(d.type !== "detectResult") return;
done();
console.log("detect result:", d);
}
window.addEventListener("message", onMsg);
t = setTimeout(function(){
done();
console.warn("detect timeout");
}, 1500);
window.postMessage({
origin: pageOrigin,
type: "detect",
requestId: requestId
}, "*");
})();
Raw command example
DOM actions are sent as type:"command" with an action name and a payload object.
// Raw command (example: getText)
(function(){
const requestId = "cmd_" + Date.now() + "_" + Math.random().toString(16).slice(2);
const pageOrigin = window.RTO_PAGE_ORIGIN || "rto-docs";
let t;
function done(){
if(t) clearTimeout(t);
window.removeEventListener("message", onMsg);
}
function onMsg(ev){
const d = ev.data;
if(ev.source !== window) return;
if(!d || typeof d !== "object") return;
if(d.origin !== pageOrigin) return;
if(d.requestId !== requestId) return;
if(d.type !== "commandResult") return;
done();
console.log("command result:", d);
}
window.addEventListener("message", onMsg);
t = setTimeout(function(){
done();
console.warn("command timeout");
}, 5000);
window.postMessage({
origin: pageOrigin,
type: "command",
requestId: requestId,
action: "getText",
payload: {
tabKey: "demoTab",
selector: "h1"
}
}, "*");
})();
Note
Even if you write raw messages, you still need allow-list permission and a controlled tab for most actions.