就像在腳本組件與擴充功能的執行階段,這段章節所提到的,腳本組件之間無法直接進行溝通,要依靠事件趨動的原理來處理各種操作邏輯,接下來我們就來探討這部份的實作細節及原理。
一次性請求
訊息的發送
相關API:
runtime.sendMessage
及
tabs.sendMessage
chrome.runtime.sendMessage(string extensionId, any message, object options, function responseCallback)
chrome.tabs.sendMessage(integer tabId, any message, function responseCallback)
兩個API的使用方法類似,以下是用法歸納:
runtime.sendMessage:向擴充功能內的其他部件傳送訊息(!! 但內容腳本除外),此外使用這個方法也可以向其他的擴充功能傳遞訊息,此時需附上另一個擴充功能的ID,如果省略ID,則消息只會在擴充功能內部傳送。
tabs.sendMessage:把訊息傳送給內容腳本的專屬API,需附上tabID作為參數,讓Chrome知道他要傳送訊息的對像是哪個內容腳本。(注意不同頁籤之間並不共享內容腳本,內容腳本在每個頁籤都會單獨注入)。
回調在接收到訊息的回傳時,會將回傳時作為參數提供。
以下程式碼範例,示範了從內容腳本發送單次請求。並且在回調中可使用response參數接收訊息的回傳。
chrome.runtime.sendMessage({greeting: "你好"}, function(response) {
console.log(response);
如果從內容腳本向擴充功能執行緒發送請求,與上面的作法類似,唯一的差別就是需指定發送對象,所以要把tabID作為參數傳入。
以下程式碼示範了,利用chrome.tabs.query取得當前tabID(tabs[0].id),並向目前使用者Focus的頁籤發送請求。
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
chrome.runtime.onMessage.addListener(function callback)
設置chrome.runtime.onMessage.addListener來接收訊息:
訊息的接收方式是所有腳本共用的,內容腳本也可以使用此方法得到訊息。
回調事件會回傳sender,sender內含資訊根據腳本的不同也會有所不同,如果sender中包含tab資訊,代表他來自內容腳本,其他則是來自擴充功能執行階段的訊息。
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"取得到tab,這是來自內容腳本的訊息:" + sender.tab.url
: "沒有tab,這是來自擴充功能內部的訊息");
if (request.greeting == "你好")
sendResponse({farewell: "再見"});
如果有多個訂閱的設置(onMessage)同時都回傳訊息,只有其中一個sendResponse()能發送成功。
動手作看看:一次性請求
實作功能說明:
實作一個頁面按鈕,點擊後跳出彈出視窗,並且畫面上擁有兩個按鈕。
點擊【向事件腳本發送訊息】,彈出視窗腳本將傳送訊息給事件腳本,並接收回傳。
點擊【向內容腳本發送訊息】,彈出視窗腳本將傳送訊息給內容腳本,接收回傳。並且內容腳本在接收到訊息的時後,會改變載入網頁的顏色。
彈出視窗腳本:
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);
事件腳本:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
console.log(message);
console.log(sender);
sendResponse({content: "來自事件腳本的回覆"});
內容腳本:
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
chrome.runtime.connect(string extensionId, object connectInfo)
chrome.tabs.connect(integer tabId, object connectInfo)
不管是發送還是接收,腳本都會獲得一個回傳的 runtime.Port物件,我們將通過他來接收跟發送訊息。
以下腳本示範如何從內容腳本建立連接,並且發送及監聽訊息。
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
chrome.runtime.onConnect.addListener(function callback)
通訊發生時,回調會回傳port物件,供你接收及發送訊息。
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
一次性的訊息發送,請使用 runtime.sendMessage 或tabs.sendMessage。
一次性訊息的接收,請使用 runtime.onMessage
長時間通訊,請使用 runtime.connect 或 tabs.connect,此API會回傳一個rutime.port物件,你可以使用他的onMessage以及postMessage接收及發送訊息。
長時間通訊事件的接收,請使用runtime.onConnect,同上回調中會提供你rutime.port物件,供你發送及接收訊息。
注意要對內容腳本傳遞訊息,都要使用tabs底下的API(tabs.sendMessage 以及tabs.connect ),並且使用tabs.query取得想發送的頁籤ID。至於內容腳本的訊息接收跟其他腳本組件並無差異(內容腳本可以正常使用runtime.onMessage以及runtime.onConnect )。
腳本之間的溝通我覺得是開發擴充功能的一個大重點,所以我想把範例作細一點,因此拆成上下章,下一個章節我們將繼續探討各種腳本之間的溝通議題。
https://crxdoc-zh.appspot.com/extensions/runtime
https://crxdoc-zh.appspot.com/extensions/tabs