Chrome扩展通信总结

我们知道chrome 插件主要是由内容脚本,后台脚本,工具栏popup脚本处理逻辑,但是内容脚本只能调用一些指定的chrome api。后台脚本不能操作dom,所以就需要一个通信系统来让他们交互起来。下面我们来看看具体通信方式吧。

主要有两大类api供我们进行通信。

一次性通信

内容脚本向popup,后台脚本通信

内容脚本向后台脚本和工具栏popup脚本发送消息我们可以使用chrome.runtime.sendMessageAPI。

js 复制代码
// 内容脚本
chrome.runtime.sendMessage({type: "content => background,popup"}, (response) => {
     console.log("======", response) // 后台,popup脚本回复
})
js 复制代码
// 后台脚本
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log("======background", request)
    sendResponse("background回复")
  }
);
js 复制代码
// popup脚本
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log("======popup", request)
    sendResponse("popup回复")
  }
);

如果background和popup同时监听,那么它们都可以同时收到消息,但是只有一个可以sendResponse,一个先发送了,那么另外一个再发送就无效。

popup,后台脚本向内容脚本通信

后台脚本和工具栏popup脚本向内容脚本发送消息我们可以使用chrome.tabs.sendMessageAPI

这里需要注意一下,如果后台脚本向内容脚本发送消息可能会报Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist.错误,这个错误通常发生在尝试在内容脚本启动之前向内容脚本发送消息时。由于内容脚本和后台脚本在不同的上下文中运行,后台脚本可能在内容脚本开始运行之前就尝试发送消息,导致无法建立连接的错误。所以后台脚本需要在一定条件下触发消息。比如点击工具栏图标。

js 复制代码
// 后台脚本
chrome.action.onClicked.addListener(async (tab) => {
  chrome.tabs.sendMessage(tab.id, { data: 'focus-off' });
});
js 复制代码
// popup脚本
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
  chrome.tabs.sendMessage(tabs[0].id, {type: 'popup => content'}, (response) => {
    console.log("回复message", response)
  })
})
js 复制代码
// 内容脚本
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log("======background, popup", message)
  sendResponse("content回复消息")
})

长连接通信

如需创建可重复使用的长期消息传递通道,请调用 runtime.connect() 以将消息从内容脚本传递到扩展程序页面,或调用 tabs.connect() 以将消息从扩展程序页面传递到内容脚本。

内容脚本向popup,后台脚本通信

js 复制代码
// 后台脚本
chrome.runtime.onConnect.addListener(function(port) {
  // port中存在 disconnect, onDisconnect, onMessage, postMessage, sender属性和方法
  port.onMessage.addListener(function(info) {
    console.log('at line 52 in email_this_page/background.js:', info, port, port.sender)
    // 回复消息
    port.postMessage("我收到消息了")
  });
});
js 复制代码
// 内容脚本
const additionalInfo = {
  "title": document.title,
  "selection": window.getSelection().toString() // 获取选中的字符串
};

const port = chrome.runtime.connect({name: "first-connect"})

port.postMessage(additionalInfo);
port.onMessage.addListener(function(info) {
  console.log('at line 14 in email_this_page/content_script.js:', info)
});
js 复制代码
// popup脚本
chrome.runtime.onConnect.addListener(function(port) {
  // port中存在 disconnect, onDisconnect, onMessage, postMessage, sender属性和方法
  port.onMessage.addListener(function(info) {
    console.log('at line 52 in email_this_page/background.js:', info, port, port.sender)
    port.postMessage("oopup-我收到消息了")
  });
});

如果popup,后台脚本都监听消息,并且回复消息,那么内容脚本都可以接收到,而不是向短连接一样,只能接收第一个回复消息的。这也是长连接的一种作用,持续接收消息。

popup,后台脚本向内容脚本通信

这种通信方式经过测试发现总是报Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.错误。并且发现chrome.action.onClicked.addListener在设置了default_popup后,在后台脚本和popup脚本中都无法监听到点击。

js 复制代码
// 后台脚本
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    const port = chrome.tabs.connect(tabs[0].id, {name: "backgroundName"});
    // 发送消息
    port.postMessage({type: "background => content"});
    // 接收消息
    port.onMessage.addListener(function getResp(response) {
      console.log("background:", response)
    });

    
    // 可选:监听连接关闭事件
    port.onDisconnect.addListener(() => {
      console.log("Port disconnected");
    });
  });
js 复制代码
// popup脚本
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    const port = chrome.tabs.connect(tabs[0].id, {name: "popupName"});
    // 发送消息
    port.postMessage({type: "popup => content"});
    // 接收消息
    port.onMessage.addListener(function getResp(response) {
      console.log("popup:", response)
    });
  });
js 复制代码
// 内容脚本
chrome.runtime.onConnect.addListener((port) => {
    console.log('at line 113 in content/content.js:', port)
  })  

内容脚本与寄主环境通信

由于内容脚本默认情况下是独立于寄主环境执行的,但是可以操作dom,他们可以通过下面几种方式进行通信。

  • 可以通过window.postMessagewindow.addEventListener来实现二者消息通讯。注意这里不能进行双向通信,因为通过e.source指向的是发送方,而发送方也就是当前window对象,那么再通过window对象发送就会造成循环发送。
js 复制代码
// 内容脚本
window.onmessage = (e) => {
     console.log("寄主环境传递的消息", e)
     // e.source.postMessage(
     //      "content回复",
     //      e.origin,
     // );
}
js 复制代码
// 注入页面
document.getElementById("mybutton").addEventListener("click", () => {
  window.postMessage({type: "injected => content"}, '*');
  // window.onmessage = (e) => {
  //   console.log("内容脚本回信", e)
  // }
}, false);
  • 通过自定义DOM事件来实现
js 复制代码
// 注入页面
  document.getElementById('mybutton').addEventListener('click', () => {
    const myEvent = new CustomEvent("myEvent", {detail: {
      type: "inject => content"
    }})
    window.dispatchEvent(myEvent)
  })
js 复制代码
// 内容脚本
window.addEventListener("myEvent", (e) => {
     console.log("e", e)
})
  • 将内容脚本和寄主环境共享执行环境。在manifest中配置内容脚本world: "MAIN"

扩展间通信

和在同一个扩展中进行长连接一样,如果popup,background同时监听消息,并且回复消息,那么都可以监听到。但是如果当前内容脚本是向指定扩展发送的消息,当前扩展的其他脚本是不能监听到的。

js 复制代码
// 当前扩展内容脚本
const additionalInfo = {
  "title": document.title,
  "selection": window.getSelection().toString() // 获取选中的字符串
};
// 向chromev3demo3通信,需要指定其扩展id。
const port = chrome.runtime.connect("ochkjkbancdeogkponljmdkglimpbkfa", {name: "first-connect"})

port.postMessage(additionalInfo);
port.onMessage.addListener(function(info) {
  console.log('at line 14 in manifestv3demo4/content_script.js:', info)
});
js 复制代码
// 其他扩展的后台脚本
chrome.runtime.onConnectExternal.addListener(function(port) {
  // port中存在 disconnect, onDisconnect, onMessage, postMessage, sender属性和方法
  port.onMessage.addListener(function(info) {
    console.log('at line 52 in manifestv3demo3/background.js:', info, port, port.sender)
    port.postMessage("background03-我收到消息了")
  });
});
js 复制代码
// 其他扩展的popup脚本
chrome.runtime.onConnectExternal.addListener(function(port) {
  // port中存在 disconnect, onDisconnect, onMessage, postMessage, sender属性和方法
  port.onMessage.addListener(function(info) {
    console.log('at line 106 in manifestv3demo3/popup.js:', info, port, port.sender)
    port.postMessage("popup03-我收到消息了")
  });
});
js 复制代码
// 内容脚本
chrome.runtime.sendMessage("ochkjkbancdeogkponljmdkglimpbkfa", additionalInfo, (msg) => {
console.log("响应的", msg)
})
js 复制代码
// 后台脚本
chrome.runtime.onMessageExternal.addListener((message, sender, messageRresponse) => {
  console.log("background03", message, sender)
  messageRresponse("background03 => content04")
  
  // return true
})
js 复制代码
// popup脚本
chrome.runtime.onMessageExternal.addListener((message, sender, messageRresponse) => {
  console.log("popup03", message, sender)
  messageRresponse("popup03 => content04")
  // return true
})

本质上讲,就是使用

  • 一次性通信,chrome.runtime.sendMessage, chrome.runtime.onMessage.addListener, chrome.tabs.sendMessage
  • 长连接通信,chrome.runtime.connect, chrome.runtime.onConnect.addListener, chrome.tabs.connect,
  • 跨扩展通信 chrome.runtime.sendMessage, chrome.runtime.connect, chrome.runtime.onMessageExternal.addListener, chrome.runtime.onConnectExternal.addListener

进行监听,但是经过一些测试发现,总是有一些出入,这里记录一下,如果项目中遇到问题再来补充。

往期年度总结

往期文章

专栏文章

相关推荐
速盾cdn25 分钟前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学127 分钟前
CSS浮动
前端·css·css3
什么鬼昵称27 分钟前
Pikachu-csrf-CSRF(POST)
前端·csrf
golitter.44 分钟前
Vue组件库Element-ui
前端·vue.js·ui
golitter.1 小时前
Ajax和axios简单用法
前端·ajax·okhttp
雷特IT1 小时前
Uncaught TypeError: 0 is not a function的解决方法
前端·javascript
长路 ㅤ   2 小时前
vite学习教程02、vite+vue2配置环境变量
前端·vite·环境变量·跨环境配置
亚里士多没有德7752 小时前
强制删除了windows自带的edge浏览器,重装不了怎么办【已解决】
前端·edge
micro2010142 小时前
Microsoft Edge 离线安装包制作或获取方法和下载地址分享
前端·edge
.生产的驴2 小时前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript