I have two scripts. Each runs on a different subdomain of our company "Example.com".
Script #1 -- house.example.com
Script #2 -- bob.fred.example.com
Same domain, different subdomains.
When a particular element appears on house.example.com
, I need to send a message over to the script running on bob.fred.example.com
Since Google extensions can exchange messages between extensions, there must be a way with TamperMonkey to exchange messages within the same extension, between scripts -- especially if they run on the same second-level domain.
Can anyone point me in the right direction? An example or two would be worth their weight in gold.
Update: Although Gothdo referenced Javascript communication between browser tabs/windows as containing an answer to this question, he failed to take into consideration the cross-origin policies involved. None of the answers in that referenced question provide a clear answer for cross-origin browser tab communications, which was the main point of this question. I have now researched and solved this problem, getting ideas from a number of SO and non-SO sources. If this question is re-opened, I will post my solution.
Using @grant
enables the sandbox, which can sometimes result in difficulties when trying to interact with complicated page objects on Greasemonkey.
If you do not want to enable the sandbox with @grant
, another option is to have the userscript create an iframe to the other domain, and then post a message to it. On the other domain, in the iframe, listen for messages. When a message is received, use BroadcastChannel
to send the message to every other tab on that other domain, and your other tabs with the userscript running can have the same BroadcastChannel open and listen for messages.
For example, to create a userscript on stackoverflow.com
that can send a message to a userscript running in a different tab on example.com
:
// ==UserScript==
// @name 0 Cross-tab example
// @include /^https://example\.com\/$/
// @include /^https://stackoverflow\.com\/$/
// @grant none
// ==/UserScript==
if (window.location.href === 'https://example.com/') {
const broadcastChannel = new BroadcastChannel('exampleUserscript');
if (window.top !== window) {
// We're in the iframe:
window.addEventListener('message', (e) => {
if (e.origin === 'https://stackoverflow.com') {
broadcastChannel.postMessage(e.data);
}
});
} else {
// We're on a top-level tab:
broadcastChannel.addEventListener('message', (e) => {
console.log('Got message', e.data);
});
}
} else {
// We're on Stack Overflow:
const iframe = document.body.appendChild(document.createElement('iframe'));
iframe.style.display = 'none';
iframe.src = 'https://example.com';
setTimeout(() => {
iframe.contentWindow.postMessage('Sending message from Stack Overflow', '*');
}, 2000);
}
This results in:
If you want two-way communication, not just one-way communication, have both parent pages create a child iframe to a single target domain (say, to example.com
). To communicate to other tabs, post a message to the child iframe. Have the child iframe listen for messages, and when seen, post a BroadcastChannel message to communicate with all other iframes. When an iframe receives a BroadcastChannel message, relay it to the parent window with postMessage
.
// ==UserScript==
// @name 0 Cross-tab example
// @include /^https://example\.com\/$/
// @include /^https://(?:stackoverflow|stackexchange)\.com\/$/
// @grant none
// ==/UserScript==
if (window.location.href === 'https://example.com/') {
const broadcastChannel = new BroadcastChannel('exampleUserscript');
if (window.top !== window) {
// We're in an iframe:
window.addEventListener('message', (e) => {
console.log('iframe received message from top window');
if (e.origin === 'https://stackoverflow.com' || e.origin === 'https://stackexchange.com') {
broadcastChannel.postMessage(e.data);
}
});
broadcastChannel.addEventListener('message', (e) => {
console.log('iframe received message from BroadcastChannel');
window.top.postMessage(e.data, '*');
});
}
} else {
// We're on Stack Overflow or Stack Exchange
const iframe = document.body.appendChild(document.createElement('iframe'));
iframe.style.display = 'none';
iframe.src = 'https://example.com';
window.addEventListener('message', (e) => {
if (e.origin === 'https://example.com') {
console.log(`Top window ${window.origin} received message from iframe:`, e.data);
}
});
if (window.location.href === 'https://stackoverflow.com/') {
setTimeout(() => {
console.log('stackoverflow posting message to iframe');
iframe.contentWindow.postMessage('Message from stackoverflow', '*');
}, 2000);
}
}
In the above code, a tab on Stack Overflow sends a message to a tab on Stack Exchange. Result screenshot:
You can use GM_getValue
, GM_setValue
& GM_addValueChangeListener
to achieve cross-tab user script communication.
Add the following lines in your user script header.
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addValueChangeListener
The following lines of rough code will simplify the cross-tab user script communication.
function GM_onMessage(label, callback) {
GM_addValueChangeListener(label, function() {
callback.apply(undefined, arguments[2]);
});
}
function GM_sendMessage(label) {
GM_setValue(label, Array.from(arguments).slice(1));
}
So all you'll need to do is the following to send and receive messages.
GM_onMessage('_.unique.name.greetings', function(src, message) {
console.log('[onMessage]', src, '=>', message);
});
GM_sendMessage('_.unique.name.greetings', 'hello', window.location.href);
NOTE Sending messages may not trigger your callback if the message sent is the same as before. This is due to GM_addValueChangeListener
not firing because the value has not changed, i.e. same value as before even though GM_setValue
is called.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With