简体   繁体   中英

Background.js to content.js using Port & Long-lived messaging

I've accomplished sending a single message from background.js to content.js using chrome.runtime.onMessage.addListener but I need to send a lot of messages throughout the extensions lifecycle, therefore I need to open a port and use long-lived messaging .

However, I can't find any working examples of opening a port and sending messages/objects between a background.js file and a content.js file. Does anyone have a decent example that works?

I've also run into issues with the following when trying the example in the official docs "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist."

Any help with a modern working 'long-lived messaging' example between a background.js and content.js file would be much appreciated:D

As requested here is the code I'm currently using that works for single message sending. I'd just like to change this so that I can send lots of messages, not just one.

// Content.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {           
        if (request.message == 'dataready') {
            //Do something
            sendResponse({});
            return true;   
        }        
    }
);



// Background.js
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {          
    if (changeInfo.status == 'complete') {   
        chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
            chrome.tabs.sendMessage(tabs[0].id, {
                message: 'dataready',
                action: dataForiFrame
            }, 
            function(response) {

            });  
        });
    }
});

Unless you send hundreds of messages per second there's probably no need to use port-based messaging but let's see how it might look.

Your setup is relatively complicated so you would need to maintain a Map of tabId to port relations.

Handshake initiated in content script

The content script would initiate chrome.runtime.connect, the background script would listen in onConnect and update the mapping. Then tabs.onUpdated listener will use this map to send the message to the right port.

content script:

const port = chrome.runtime.connect({name: 'content'});
port.onMessage.addListener(msg => {
  console.log(msg.data);
  // send a response if needed, may be a simple object/array
  port.postMessage({id: msg.id, data: 'gotcha'}); 
});

background script:

const portMap = new Map();
const resolveMap = new Map();
let messageId = 0;

chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete') {
    const response = await send(tabId, {message: 'dataready', action: 'foo'});
    console.log(response);
  }
});

chrome.runtime.onConnect.addListener(port => {
  portMap.set(port.sender.tab.id, port);
  port.onDisconnect.addListener(onPortDisconnected);
  port.onMessage.addListener(onPortMessage);
});

function onPortDisconnected(port) {
  portMap.delete(port.sender.tab.id);
}

function onPortMessage(msg, port) {
  resolveMap.get(msg.id)(msg.data);
  resolveMap.delete(msg.id);
}

function send(tabId, data) {
  return new Promise(resolve => {
    const id = ++messageId;
    resolveMap.set(id, resolve);
    portMap.get(tabId).postMessage({id, data});
  });
}

This is a very barebone example without any error-checking. Also note, it's not the only or the best solution. Depending on other factors that aren't present in the question there could be other solutions.


Handshake initiated in background script

For example, it's possible to reverse the handshake and call chrome.tabs.connect in the background script to connect to onConnect inside the content script.

content script:

chrome.runtime.onConnect.addListener(port => {
  port.onMessage.addListener(msg => {
    console.log(msg.data);
    // send a response if needed, may be a simple object/array
    port.postMessage({id: msg.id, data: 'gotcha'}); 
  });
});

background script:

const portMap = new Map();
const resolveMap = new Map();
let messageId = 0;

chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete') {
    const response = await send(tabId, {message: 'dataready', action: 'foo'});
    console.log(response);
  }
});

function onPortDisconnected(port) {
  portMap.delete(port.sender.tab.id);
}

function onPortMessage(msg, port) {
  resolveMap.get(msg.id)(msg.data);
  resolveMap.delete(msg.id);
}

function send(tabId, data) {
  return new Promise(resolve => {
    const id = ++messageId;
    let port = portMap.get(tabId);
    if (!port) {
      port = chrome.tabs.connect(tabId, {frameId: 0});
      port.onDisconnect.addListener(onPortDisconnected);
      port.onMessage.addListener(onPortMessage);
      portMap.set(tabId, port);
    }
    resolveMap.set(id, resolve);
    port.postMessage({id, data});
  });
}

PS The problem with examples is that people tend to copy them without understanding the mechanics. For instance, your code seems to needlessly use chrome.tabs.query inside onUpdated listener even though it already has tabId parameter, thus assuming it's always the active tab that got updated even when it's not so.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM