腳本組件之間訊息的傳遞(下)
請與上一篇一起服用。要真的弄懂擴充功能的事件系統,請先對擴充功能的組成有清楚的概念。如果你覺得自己在理解上有困難,是時後回到前面複習一下:Chrome Extension 開發與實作 04-名詞定義:架構的組成部份
跨擴充功能之間的溝通
Chrome允許擴充功能接受其他擴充功能傳送的訊息,你可以提供一個公用的API,讓其他擴充功能使用。
外部訊息的發送:
相關API: runtime.sendMessage 以及 runtime.connect
與擴充功能的其他部份並無差別(請參考上一篇),記得使用擴充功能的ID作為傳入extensionId
參數,以指定發送對象。
一次性請求
1
| chrome.runtime.sendMessage(string extensionId, any message, object options, function responseCallback)
|
長時間連接
1
| chrome.runtime.connect(string extensionId, object connectInfo)
|
外部訊息請求的接受:
相關API:runtime.onMessageExternal 以及 runtime.onConnectExternal
一次性接收
1
| chrome.runtime.onMessageExternal.addListener(function callback)
|
長時間連接的接受:同樣的回調中會提供runtime.port物件供你發送及接收訊息
1
| chrome.runtime.onConnectExternal.addListener(function callback)
|
內容腳本不能存取外部訊息,如果有此需求需經由內部的事件腳本將訊息傳遞給內部的內容腳本。
動手作看看(1):跨擴充功能之間的溝通
實作功能說明:
擴充功能A的事件腳本:
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 29 30 31 32 33
| var toggle = false; //可以列出禁止防問的黑名單ID var blockList = []; chrome.runtime.onMessageExternal.addListener(function(message, sender, sendResponse) { console.log(sender); //如果訪問在黑名單,就不作任何動作 if (blockList.indexOf(sender.id) != -1) { return; } if (message.name != "切換頁面按鈕") { return; } //如果按鈕是啟用的狀態則開,否則關 if (!toggle) { chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { chrome.pageAction.show(tabs[0].id); toggle = !toggle; }); } else { chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { chrome.pageAction.hide(tabs[0].id); toggle = !toggle; }); } sendResponse("來自擴充功能A的訊息:操作完成"); });
|
擴充功能B的彈出視窗腳本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| document.addEventListener('DOMContentLoaded', function(dcle) { var dButton = document.getElementById("button"); //注意擴充功能A的ID在開始狀態是會變的,如果你要在你的地端跑這個範例 //請自行在擴充功能管理頁面查看ID //一旦正式發佈你的擴充功能,你可以得到一組固定ID var extensionID = "fhggngpimllbngfbjdbiplgmhdppiiop"; //點擊按鈕,向擴充功能A發動訊息 dButton.addEventListener('click', function(e) { console.log("click"); chrome.runtime.sendMessage( extensionID, { name: "切換頁面按鈕" }, function(response) { console.log(response); }); }); });
|
注意:擴充功能A的ID在是會變的,如果你要在你的地端跑這個範例,請自行在擴充功能管理頁面查看ID, 一旦正式發佈你的擴充功能,你可以得到一組固定ID
結果展示:
右下擴充功能A的事件頁面,右上擴充功能B的彈出視窗腳本

完整範例在:Github
續跨擴充功能之間的溝通-來自網頁的外部訊息
就像上面跨擴充功能溝通的例子雷同,事實上擴充功能也能接收並且響應來自普通網頁的訊息,要能讓網站可以用使用Chrome的訊息API,首先你得在設定檔中列出允許傳遞訊息的網域。
1 2 3
| "externally_connectable": { "matches": ["*://*.example.com/*"] }
|
網址的匹配表執式必需至少包含一個二級域名,也就是禁止使用“”、“.com”、“.co.uk”以及“.appspot.com”之類的主機名作為匹配表達式。
動手作看看(2): 接收來自網站腳本的訊息
實作功能說明:
- 實作一個網站,網頁上有一個按鈕。
- 實作一個擴充功能,擁有頁面按鈕,非啟用的狀態。
- 按一下網頁上的按鈕,擴充功能的頁面按鈕即啟用,再按一下關閉。
設定檔
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { "manifest_version": 2, "name": "訊息範例-頁面腳本向事件腳本發送訊息", "description": "頁面腳本向事件腳本發送訊息", "version": "2.0", "page_action": { "default_title": "頁面腳本向事件腳本發送訊息", "default_icon": "icon.png", "default_popup": "popup.html" }, "background": { "scripts": ["event.js"], "persistent": false }, "externally_connectable": { "matches": ["*://localhost/*"] }, "permissions": ["tabs"] }
|
事件腳本
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 29 30 31 32 33
| var toggle = false; //可以列出禁止防問的黑名單網址 var blockList = []; chrome.runtime.onMessageExternal.addListener(function(message, sender, sendResponse) { console.log(sender); //如果訪問在黑名單,就不作任何動作 if (blockList.indexOf(sender.url) != -1) { return; } if (message.name != "切換頁面按鈕") { return; } //如果按鈕是啟用的狀態則開,否則關 if (!toggle) { chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { chrome.pageAction.show(tabs[0].id); toggle = !toggle; }); } else { chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { chrome.pageAction.hide(tabs[0].id); toggle = !toggle; }); } sendResponse("來自擴充功能的訊息:操作完成"); });
|
網頁HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <!DOCTYPE html> <html itemscope itemtype="http://schema.org/Article" ng-app="markup" debug="false"> <!--page default Information--> <!--html head--> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="edge,chrome=1"> <title>首頁</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flat-ui/2.3.0/css/flat-ui.css"> </head> <!--html body--> <body class="l-index"> <section class="index"> <p style="padding:100px;"><a id="button" href="#" class="btn btn-primary btn-large btn-block">切換擴充功能的頁面按鈕</a></p> </section> </body> <script src="../js/page.js"></script> </html>
|
網頁腳本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var dButton = document.getElementById("button"); var extensionID = "ampdcffgccbnfionpljcpncpcjfbhbag"; //點擊按鈕,向擴充功能A發動訊息 dButton.addEventListener('click', function(e) { console.log("click"); chrome.runtime.sendMessage( extensionID, { name: "切換頁面按鈕" }, function(response) { console.log(response); }); });
|
結果展示

完整範例:Github
內容腳本以及頁面腳本的溝通
前面已經提過,內容腳本沒辦法存取runtime.onMessageExternal 以及 runtime.onConnectExternal API,因此內容腳本當然沒辦法像上面的案例一樣使用這個途勁與網頁的腳本溝通。
作為替代方案,內容腳本可以使用標準的網頁Javascript API。(注意:非chrome *API)
訊息的發起:
相關API:Window.postMessage
1
| window.postMessage(message, targetOrigin, [transfer]);
|
下面示範內容腳本在頁面插入一個按鈕,當使用者按下按鈕時,對頁面腳本發送訊息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| //region {variables and functions} var consoleGreeting = "Hello World!"; var targetOrigin = window.location.origin; var message = "Test message X"; function createButton() { var button = document.createElement("button"); button.style.width = "70px"; button.style.height = "40px"; button.style.position = "fixed"; button.style.top = "10px"; button.style.right = "10px"; button.innerText = "Send Message"; document.body.appendChild(button); return button; } //end-region //region {calls} console.log(consoleGreeting); var button = createButton(); button.addEventListener("click",function() { console.log("Button clicked!"); window.postMessage(message,targetOrigin); });
|
訊息的接收:
相關API:Window.addEventListener
window.addEventListener("message", listener[, options]);
下面示範一個網頁腳本接收來自頁面腳本的訊息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| //region {variables and functions} var sendMessageButtonID = "send_message"; var greeting = "Hello World!"; //var targetOrigin = window.location.origin; //var message = "Test message Y"; //end-region //region {calls} console.log(greeting); document.addEventListener("DOMContentLoaded",function(dcle) { var buttonID = document.getElementById(sendMessageButtonID); buttonID.addEventListener("click",function(ce) { //window.postMessage(message,targetOrigin); }); }); window.addEventListener("message",function(me) { console.log("message: " + me.data); }); //end-region
|
本來想作範例,但我想了一下覺得這種使用情景實在不多,這裡就不示範了。
如果大家對這個段落的範例有興趣,這裡使用的是電子書中的範例,完整的範例參考第三章節的CSandWS範例
補充(1):腳本訊息傳遞的安全性
要小心跨站腳本攻擊,避免使用以下API:
1 2 3 4
| chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // 警告:可能會執行惡意腳本 var resp = eval("(" + response.farewell + ")"); });
|
1 2 3 4
| chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // 警告:可能會插入惡意腳本 document.getElementById("resp").innerHTML = response.farewell; });
|
首選以下API:
1 2 3 4
| chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // JSON.parse 不會攻擊者的執行腳本。 var resp = JSON.parse(response.farewell); });
|
1 2 3 4
| chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // innerText 不會插入攻擊者的html (因為可能會包含惡章腳本)。 document.getElementById("resp").innerText = response.farewell; });
|
補充(2):釐清標準JS API 與 Chrome JS API的事件系統
不要混肴chrome extension API提供的事件監聽系統,跟JS的DOM事件監聽系統。 寫法不同,用法不同:
Chrome Etension 的 事件監聽API:(chrome.* API)
1 2 3 4 5 6
| chrome.commands.onCommand.addListener(function(command) { chrome.browserAction.setIcon( details, function() {/**/} ); });
|
1 2 3 4
| chrome.tabs.onUpdated.addListener(function(tabId,changeInfo,tab) { console.log(tabId); chrome.pageAction.show(tabId); });
|
JS的DOM物件事件監聽API: (Javascript API)
1
| el.addEventListener("click", modifyText, false);
|
小結
花絮
訊息系統真的是繞的我蠻暈的,每次寫訊息相關的系統都覺得自己人格分裂的應該不只我一人。
參考