The communication scheme is simple:
- your devtools panel opens a port to the background page
- the background page listens on that port and stores a lookup table of tabId-to-port mappings
- the background page also listens for messages from content scripts and uses the above lookup table to route the message to a corresponding port channel
devtools-panel.js
var port = chrome.runtime.connect();
port.onMessage.addListener(message => {
$id('insertmessagebutton').innerHTML = message.content;
});
$id('executescript').onclick = () =>
runContentScript({code: "console.log('Content script executed')"});
$id('insertscript').onclick = () =>
runContentScript({file: "inserted-script.js"});
$id('insertmessagebutton').onclick = () => {
runContentScript({code: "document.body.innerHTML='<button>Send to panel</button>'"});
runContentScript({file: "messageback-script.js"});
};
function runContentScript(params) {
port.postMessage(
Object.assign({
tabId: chrome.devtools.inspectedWindow.tabId,
}, params)
);
}
function $id(id) {
return document.getElementById(id);
}
background.js
var tabPorts = {};
chrome.runtime.onConnect.addListener(port => {
let tabId;
port.onMessage.addListener(message => {
if (!tabId) {
// this is a first message from devtools so let's set the tabId-port mapping
tabId = message.tabId;
tabPorts[tabId] = port;
}
if (message.code || message.file) {
delete message.tabId;
chrome.tabs.executeScript(tabId, message);
}
});
port.onDisconnect.addListener(() => {
delete tabPorts[tabId];
});
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
const port = sender.tab && tabPorts[sender.tab.id];
if (port) {
port.postMessage(message);
}
});
chrome.tabs.onRemoved.addListener(tabId => {
delete tabPorts[tabId];
});
chrome.tabs.onReplaced.addListener((newTabId, oldTabId) => {
delete tabPorts[oldTabId];
});