轉載來自:https://blog.csdn.net/summerxiachen/article/details/78698878
chrome 插件主要由三部分構成
1.popup
在用戶點擊擴展程序圖標時(下圖中的下載圖標),都可以設置彈出一個popup頁面。而這個頁面中自然是可以包含運行的js腳本的(比如就叫popup.js)。它會在每次點擊插件圖標——popup頁面彈出時,重新載入。
2.content_scripts
是會注入到Web頁面的JS文件,可以是多個,也可以對注入條件進行設置,也就是滿足什么條件,才會將這些js文件注入到當前web頁面中。
可以把這些注入的js 文件和網頁的個文件看成一個整體,相當於在你網頁中,寫入了這些js 代碼。這樣就可以對原來的web頁面進行操作了。
3 background 即插件運行的環境
可以是html+js, 也可以是單純的js
插件啟動后就常駐后台,只有一個。這類腳本是運行在瀏覽器后台的,注意它是與當前瀏覽頁面無關的。
在實際運行過程中
原始web+注入的的content_scripts=新的web頁面
當打開多個頁面時,就會存在多個新的web頁面。因為每個頁面都注入content_scripts。
那么在通信的時候,后台腳本或則popup頁面,怎么確定是與那個頁面進行消息交互呢,通過tabID
tab是什么呢?
上圖就有三個tab標簽,也就是在瀏覽器中打開的網頁對應着一個tab,圖中第二個和第三個雖然url相同,但tabid不一樣。
三個主要部分消息交互機制如下圖
通過上圖我們可以把交互消息分為三類:【A】【B】【C】
【A】直接發送消息一般是
//content_scripts——>background 例如
chrome.runtime.sendMessage(
{
greeting : message || '你好,我是content-script呀,我主動發消息給后台!'},
function(response) {
tip('收到來自后台的回復:' + response);
}
);
在發出方是主動發送消息,那么接收方必須時刻准備接受消息,才能保證及時接收到,所以接收方都是通過監聽這一動作來完成消息的接收
// 監聽消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
// code...
sendResponse('我已收到你的消息:' +JSON.stringify(request));//做出回應
});
【B】發送消息一般是先獲取到tabID在發送消息
function getCurrentTabId(callback)
{
chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
{
if(callback) callback(tabs.length ? tabs[0].id: null);
});
}
function sendMessageToContentScript(message, callback)
{
getCurrentTabId((tabId) =>
{
chrome.tabs.sendMessage(tabId, message, function(response)
{
if(callback) callback(response);
});
});
}
sendMessageToContentScript('你好,我是bg!', (response) => {
if(response) alert('收到來自content-script的回復:'+response);
});
【C】popup調用后台腳本中的方法
var bg = chrome.extension.getBackgroundPage();
bg.test();//test()是background中的一個方法
通信詳細介紹
1.popup 和 background
popup可以直接調用background中的JS方法,也可以直接訪問background的DOM:
// background.js
function test()
{
alert('我是background!');
}
// popup.js
var bg = chrome.extension.getBackgroundPage();
bg.test(); // 訪問bg的函數
alert(bg.document.body.innerHTML); // 訪問bg的DOM
至於background
訪問popup
如下(前提是popup
已經打開):
var views = chrome.extension.getViews({type:'popup'});
if(views.length > 0) {
console.log(views[0].location.href);
}
2.popup或者bg向content主動發送消息
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的回復:'+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('我收到了你的消息!');
});
雙方通信直接發送的都是JSON對象,不是JSON字符串,所以無需解析,很方便(當然也可以直接發送字符串)。
網上有些老代碼中用的是chrome.extension.onMessage
,沒有完全查清二者的區別(貌似是別名),但是建議統一使用chrome.runtime.onMessage
。
3.content-script主動發消息給后台
content-script.js:
chrome.runtime.sendMessage({greeting: '你好,我是content-script呀,我主動發消息給后台!'}, function(response) {
console.log('收到來自后台的回復:' + response);
});
background.js 或者 popup.js:
// 監聽來自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
主動發消息的前提是popup必須打開!否則需要利用background作中轉; - 如果background和popup同時監聽,那么它們都可以同時收到消息,但是只有一個可以sendResponse,一個先發送了,那么另外一個再發送就無效;
4.injected script和content-script
content-script
和頁面內的腳本(injected-script
自然也屬於頁面內的腳本)之間唯一共享的東西就是頁面的DOM元素,有2種方法可以實現二者通訊:
- 可以通過
window.postMessage
和window.addEventListener
來實現二者消息通訊; - 通過自定義DOM事件來實現;
第一種方法(推薦):
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);
});
5.injected-script與popup通信
injected-script
無法直接和popup
通信,必須借助content-script
作為中間人。
長連接和短鏈接
一個是短連接(chrome.tabs.sendMessage
和chrome.runtime.sendMessage
),一個是長連接(chrome.tabs.connect
和chrome.runtime.connect
)。
短連接的話就是擠牙膏一樣,我發送一下,你收到了再回復一下,如果對方不回復,你只能重新發,而長連接類似WebSocket
會一直建立連接,雙方可以隨時互發消息。
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:
// 監聽長連接
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: '我是你爸!'});
});
}
});
動態注入或執行JS
雖然在background
和popup
中無法直接訪問頁面DOM,但是可以通過chrome.tabs.executeScript
來執行腳本,從而實現訪問web頁面的DOM(注意,這種方式也不能直接訪問頁面JS)。
示例manifest.json
配置:
{
"name": "動態JS注入演示",
...
"permissions": [
"tabs", "http://*/*", "https://*/*"
],
...
}
JS:
// 動態執行JS代碼
chrome.tabs.executeScript(tabId, {code: 'document.body.style.backgroundColor="red"'});
// 動態執行JS文件
chrome.tabs.executeScript(tabId, {file: 'some-script.js'});