通信主頁:https://developer.chrome.com/extensions/messaging
前面我們介紹了Chrome插件中存在的5種JS,那么它們之間如何互相通信呢?下面先來系統(tǒng)概況一下,然后再分類細說。需要知道的是,popup和background其實幾乎可以視為一種東西,因為它們可訪問的API都一樣、通信機制一樣、都可以跨域。
1.互相通信概覽
| -- | injected-script | content-script | popup-js | background-js |
|---|---|---|---|---|
| injected-script | 無 | window.postMessage | 無 | 無 |
| content-script | window.postMessage | - | chrome.runtime.sendMessage chrome.runtime.connect | chrome.runtime.sendMessage chrome.runtime.connect |
| popup-js | - | chrome.tabs.sendMessage chrome.tabs.connect | - | chrome.extension. getBackgroundPage() |
| background-js | - | chrome.tabs.sendMessage | chrome.tabs.connect chrome.extension.getViews | - |
| devtools-js | chrome.devtools. inspectedWindow.eval | - | chrome.runtime.sendMessage | chrome.runtime.sendMessage |
注:-表示不存在或者無意義,或者待驗證。
2.通信詳細介紹
2.1 popup和background
popup可以直接調(diào)用background中的JS方法,也可以直接訪問background的DOM:
// background.js
function test()
{
alert('我是background!');
}
// popup.js
var bg = chrome.extension.getBackgroundPage();
bg.test(); // 訪問bg的函數(shù)
alert(bg.document.body.innerHTML); // 訪問bg的DOM
小插曲,今天碰到一個情況,發(fā)現(xiàn)popup無法獲取background的任何方法,找了半天才發(fā)現(xiàn)是因為background的js報錯了,而你如果不主動查看background的js的話,是看不到錯誤信息的,特此提醒。
至于background訪問popup如下(前提是popup已經(jīng)打開):
var views = chrome.extension.getViews({type:'popup'});
if(views.length > 0) {
console.log(views[0].location.href);
}
2.2 popup或者bg向content主動發(fā)送消息
background.js或者popup.js:
function sendMessageToContentScript(message, callback)
{
chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
{
chrome.tabs.sendMessage(tabs[0].id, message, function(response)
{
if(callback) callback(response);
});
});
}
sendMessageToContentScript({cmd:'test', value:'你好,我是popup!'}, function(response)
{
console.log('來自content的回復(fù):'+response);
});
content-script.js接收:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
// console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
if(request.cmd == 'test') alert(request.value);
sendResponse('我收到了你的消息!');
});
雙方通信直接發(fā)送的都是JSON對象,不是JSON字符串,所以無需解析,很方便(當然也可以直接發(fā)送字符串)。
網(wǎng)上有些老代碼中用的是chrome.extension.onMessage,沒有完全查清二者的區(qū)別(貌似是別名),但是建議統(tǒng)一使用chrome.runtime.onMessage。
2.3 content-script主動發(fā)消息給后臺
content-script.js:
chrome.runtime.sendMessage({greeting: '你好,我是content-script呀,我主動發(fā)消息給后臺!'}, function(response) {
console.log('收到來自后臺的回復(fù):' + response);
});
background.js 或者 popup.js:
// 監(jiān)聽來自content-script的消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
console.log('收到來自content-script的消息:');
console.log(request, sender, sendResponse);
sendResponse('我是后臺,我已收到你的消息:' + JSON.stringify(request));
});
注意事項:
content_scripts向popup主動發(fā)消息的前提是popup必須打開!否則需要利用background作中轉(zhuǎn);如果background和popup同時監(jiān)聽,那么它們都可以同時收到消息,但是只有一個可以sendResponse,一個先發(fā)送了,那么另外一個再發(fā)送就無效;
2.4 injected script和content-script
content-script和頁面內(nèi)的腳本(injected-script自然也屬于頁面內(nèi)的腳本)之間唯一共享的東西就是頁面的DOM元素,有2種方法可以實現(xiàn)二者通訊:
可以通過window.postMessage和window.addEventListener來實現(xiàn)二者消息通訊;
通過自定義DOM事件來實現(xiàn);
第一種方法(推薦):
injected-script中:
window.postMessage({"test": '你好!'}, '*');
content script中:
window.addEventListener("message", function(e)
{
console.log(e.data);
}, false);
第二種方法:
injected-script中:
var customEvent = document.createEvent('Event');
customEvent.initEvent('myCustomEvent', true, true);
function fireCustomEvent(data) {
hiddenDiv = document.getElementById('myCustomEventDiv');
hiddenDiv.innerText = data
hiddenDiv.dispatchEvent(customEvent);
}
fireCustomEvent('你好,我是普通JS!');
content-script.js中:
var hiddenDiv = document.getElementById('myCustomEventDiv');
if(!hiddenDiv) {
hiddenDiv = document.createElement('div');
hiddenDiv.style.display = 'none';
document.body.appendChild(hiddenDiv);
}
hiddenDiv.addEventListener('myCustomEvent', function() {
var eventData = document.getElementById('myCustomEventDiv').innerText;
console.log('收到自定義事件消息:' + eventData);
});
3 長連接和短連接
其實上面已經(jīng)涉及到了,這里再單獨說明一下。Chrome插件中有2種通信方式,一個是短連接(chrome.tabs.sendMessage和chrome.runtime.sendMessage),一個是長連接(chrome.tabs.connect和chrome.runtime.connect)。
短連接的話就是擠牙膏一樣,我發(fā)送一下,你收到了再回復(fù)一下,如果對方不回復(fù),你只能重新發(fā),而長連接類似WebSocket會一直建立連接,雙方可以隨時互發(fā)消息。
短連接上面已經(jīng)有代碼示例了,這里只講一下長連接。
popup.js:
getCurrentTabId((tabId) => {
var port = chrome.tabs.connect(tabId, {name: 'test-connect'});
port.postMessage({question: '你是誰?。?});
port.onMessage.addListener(function(msg) {
alert('收到消息:'+msg.answer);
if(msg.answer && msg.answer.startsWith('我是'))
{
port.postMessage({question: '哦,原來是你啊!'});
}
});
});
content-script.js:
// 監(jiān)聽長連接
chrome.runtime.onConnect.addListener(function(port) {
console.log(port);
if(port.name == 'test-connect') {
port.onMessage.addListener(function(msg) {
console.log('收到長連接消息:', msg);
if(msg.question == '你是誰啊?') port.postMessage({answer: '我是你爸!'});
});
}
});