腳本組件之間訊息的傳遞(上)
就像在腳本組件與擴充功能的執行階段,這段章節所提到的,腳本組件之間無法直接進行溝通,要依靠事件趨動的原理來處理各種操作邏輯,接下來我們就來探討這部份的實作細節及原理。
一次性請求
訊息的發送
相關API: runtime.sendMessage 及 tabs.sendMessage
1
| chrome.runtime.sendMessage(string extensionId, any message, object options, function responseCallback)
|
1
| chrome.tabs.sendMessage(integer tabId, any message, function responseCallback)
|
兩個API的使用方法類似,以下是用法歸納:
runtime.sendMessage
:向擴充功能內的其他部件傳送訊息(!! 但內容腳本除外),此外使用這個方法也可以向其他的擴充功能傳遞訊息,此時需附上另一個擴充功能的ID,如果省略ID,則消息只會在擴充功能內部傳送。
tabs.sendMessage
:把訊息傳送給內容腳本
的專屬API,需附上tabID作為參數,讓Chrome知道他要傳送訊息的對像是哪個內容腳本。(注意不同頁籤之間並不共享內容腳本,內容腳本在每個頁籤都會單獨注入)。
- 回調在接收到訊息的回傳時,會將回傳時作為參數提供。
以下程式碼範例,示範了從內容腳本發送單次請求。並且在回調中可使用response
參數接收訊息的回傳。
1 2 3
| chrome.runtime.sendMessage({greeting: "你好"}, function(response) { console.log(response); });
|
如果從內容腳本向擴充功能執行緒發送請求,與上面的作法類似,唯一的差別就是需指定發送對象,所以要把tabID作為參數傳入。
以下程式碼示範了,利用chrome.tabs.query
取得當前tabID(tabs[0].id
),並向目前使用者Focus的頁籤發送請求。
1 2 3 4 5
| chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { chrome.tabs.sendMessage(tabs[0].id, { greeting: "你好" }, function(response) { console.log(response.farewell); }); });
|
chrome.tabs.query允許使用者用物件作為第一個參數查詢使用者目前開啟的所有書籤,在上面的程式碼中示範了在目前使用的視窗下,取得目前瀏覽的頁籤。回傳值會是一個tab物件,內含了網站url、title、等有用資訊。
訊息的接收
相關API: runtime.onMessage
1
| chrome.runtime.onMessage.addListener(function callback)
|
設置chrome.runtime.onMessage.addListener
來接收訊息:
- 訊息的接收方式是所有腳本共用的,內容腳本也可以使用此方法得到訊息。
- 回調事件會回傳sender,sender內含資訊根據腳本的不同也會有所不同,如果sender中包含tab資訊,代表他來自內容腳本,其他則是來自擴充功能執行階段的訊息。
1 2 3 4 5 6 7 8
| chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) { console.log(sender.tab ? "取得到tab,這是來自內容腳本的訊息:" + sender.tab.url : "沒有tab,這是來自擴充功能內部的訊息"); if (request.greeting == "你好") sendResponse({farewell: "再見"}); });
|
如果有多個訂閱的設置(onMessage)同時都回傳訊息,只有其中一個sendResponse()
能發送成功。
動手作看看:一次性請求
實作功能說明:
- 實作一個頁面按鈕,點擊後跳出彈出視窗,並且畫面上擁有兩個按鈕。
- 點擊【向事件腳本發送訊息】,彈出視窗腳本將傳送訊息給事件腳本,並接收回傳。
- 點擊【向內容腳本發送訊息】,彈出視窗腳本將傳送訊息給內容腳本,接收回傳。並且內容腳本在接收到訊息的時後,會改變載入網頁的顏色。
彈出視窗腳本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| document.addEventListener('DOMContentLoaded', function(dcle) { var dButtonEvent = document.getElementById("button1"); var dButtonContent = document.getElementById("button2"); //點擊按鈕,向事件腳本發送訊息 dButtonEvent.addEventListener('click', function(ce) { chrome.runtime.sendMessage({ content: "你好,此訊息來自彈出視窗腳本" }, function(response) { console.log(response); }); }); //點擊按鈕,向內容腳本發送訊息 dButtonContent.addEventListener('click', function(ce) { chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { chrome.tabs.sendMessage(tabs[0].id, { content: "你好,此訊息來自彈出視窗腳本" }, function(response) { console.log(response); }); }); }); });
|
事件腳本:
1 2 3 4 5
| chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { console.log(message); console.log(sender); sendResponse({content: "來自事件腳本的回覆"}); });
|
內容腳本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| console.log("內容腳本注入"); var toggleBg = true; chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { console.log(message); console.log(sender); sendResponse({ content: "來自內容腳本的回覆" }); if (toggleBg) { document.body.style.backgroundColor = "red"; toggleBg = !toggleBg; } else { document.body.style.backgroundColor = "black"; toggleBg = !toggleBg; } });
|
結果展示:向事件腳本發送訊息 (右下為彈出視窗腳本,左下為事件腳本)

結果展示:向內容腳本發送訊息 (右下為彈出視窗腳本,左下為內容腳本)

完整範例在Github
長時間連接
如果上個段落討論的一次性溝通,比喻為信件往來,那麼我們可以把長時間的連接當成打電話。
長時間連接的好處,在於通話其間雙方能共享狀態,另一方面也能讓訊息的傳遞具有針對性。讓我們來探討一個使用情境:如果你想要實作一個「表單自動填充功能」,擴充套件在內容腳本偵測到表單元素時,開啟了”表單填寫”的通話,事件腳本在接受通話的請求後,內容腳本可以在使用者聚焦到不同的表單元件時,將表單元件的訊息傳送給事件腳本,事件腳本可以判斷有無符合的資料並回傳給內容腳本自動填入。
長時間訊息的發起
相關API:runtime.connect 及 tabs.connect
1
| chrome.runtime.connect(string extensionId, object connectInfo)
|
1
| chrome.tabs.connect(integer tabId, object connectInfo)
|
不管是發送還是接收,腳本都會獲得一個回傳的 runtime.Port物件,我們將通過他來接收跟發送訊息。
以下腳本示範如何從內容腳本建立連接,並且發送及監聽訊息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| var dButtonConnect1 = document.getElementById("button1"); var dButtonConnect2 = document.getElementById("button2"); var port = chrome.runtime.connect({ name: "一通電話" }); port.onMessage.addListener(function(response) { console.log(response); switch (response.msg) { case "是的,他在": port.postMessage({ msg: "請幫我把電話她" }); break; case "不,他不在": port.postMessage({ msg: "請幫我留言給他,留言是XXXXXX" }); break; default: break; } }); dButtonConnect1.addEventListener('click', function(event) { port.postMessage({ msg: "請問羅拉拉在嗎" }); }); dButtonConnect2.addEventListener('click', function(event) { port.postMessage({ msg: "請問王小明在嗎" }); });
|
與上面提到的一次性連接範例類似,如果你是向容容腳本接立長時間連結,可以把runtime.connect,提換成 tabs.connect,並使用tabs.query
附上tabID。
長時間訊息的接受
使用 runtime.onConnect
1
| chrome.runtime.onConnect.addListener(function callback)
|
通訊發生時,回調會回傳port物件,供你接收及發送訊息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| chrome.runtime.onConnect.addListener(function(port){ if(port.name == "一通電話"){ port.onMessage.addListener(function(response) { console.log(response); switch (response.msg) { case "請問羅拉拉在嗎": port.postMessage({ msg: "是的,他在" }); break; case "請問王小明在嗎": port.postMessage({ msg: "不,他不在" }); break; default: port.postMessage({ msg: "好的" }); port.disconnect(); break; } }); } });
|
如果你想知道某個通話的連線狀態,可以調用runtime.Port.onDisconnect方法,當通話其中一方使用runtime.Port.disconnect 結束對話或是通訊的頁面被關閉,就會收到通知。
上面的範例結果展示:右上為背景頁面,右下為彈出視窗腳本

完整範例在Github
小結
參考