Tampermonkey® by Jan Biniok

Message Passing v5.6+

Userscripts can exchange messages with each other over a dedicated channel. This works both inside a single tab - between the top frame and its iframes - and between different tabs, as long as the scripts belong to the same Tampermonkey profile.

The channel is only available to sandboxed scripts. A script with @grant none or @unwrap runs in the page's own scope and uses the native window.postMessage / window.addEventListener, so message passing is not active for it. No @grant directive is required for this feature itself - every sandboxed script can use it.

Detecting availability

There is a special target, 'tampermonkey:self', that delivers the message back to your own listeners. Unlike every other target it is synchronous: the message is dispatched to your tampermonkey:message listeners in the same call, before window.postMessage returns. That makes it a reliable, timeout-free way to find out whether message passing is active for the current script:

const supportsMessagePassing = (() => {
    const token = 'tm-probe-' + Math.random().toString(36).slice(2);
    let supported = false;
    const listener = (event) => { if (event.data === token) supported = true; };
    window.addEventListener('tampermonkey:message', listener);
    try {
        window.postMessage(token, 'tampermonkey:self');
    } catch (e) {}
    window.removeEventListener('tampermonkey:message', listener);
    return supported;
})();

Listening

To receive messages, register a listener for the tampermonkey:message event:

window.addEventListener('tampermonkey:message', (event) => {
    // event.source - a unique id of the sending instance, e.g. "tampermonkey:3f2a9c…e1"
    // event.data   - the payload the sender posted
    if (event.data && event.data.hello === 'world') {
        console.log('got a greeting from', event.source);
    }
});

event.source is a stable identifier of the sending instance in the form tampermonkey: followed by a string. Every frame (the top frame and each of its iframes) and every tab has its own id, so it can be used to tell senders apart and to reply to a specific one.

Sending

To send a message, call window.postMessage with a tampermonkey: target as the second argument:

// reply to the specific instance you received the message from
window.postMessage({ hello: 'back' }, event.source);

// broadcast to every other registered instance in the same profile
window.postMessage({ type: 'status', active: true }, 'tampermonkey:*');

The target is one of:

  • an event.source id you received earlier, to send (or reply) to that single instance, or
  • the literal string 'tampermonkey:*' to broadcast the message to every other registered instance. The sending instance is never delivered to itself, so a broadcast never echoes back to its sender, or
  • the literal string 'tampermonkey:self' to deliver the message to your own listeners - the target used for feature detection (synchronous).

Use a broadcast to reach instances you have not heard from yet; once you know an instance's event.source, send to it directly.

Payload and delivery

The payload - the first argument of postMessage, delivered to the receiver as event.data - is copied, so it must be a JSON-cloneable value: an object, array, string, number, boolean or null. Functions, DOM nodes and other non-cloneable values are not supported.

Delivery is best-effort. A message only reaches instances that are currently registered - their script is running and has an open channel. There is no queueing: if the target is not registered at the moment the message is sent, the message is dropped. Because of this, a sender might re-sends a discovery message on a short interval until the receiving side is ready to pick it up.

▲