目次
1. 範例功能簡介
2. Manifest
3. Content Script & Background Script
4. 訊息傳輸
5. 參考資料
在七月時,有做了一個密碼產生的 Chrome 套件
然後我現在才在寫,可見拖多久
不過因為當時對後端還不熟悉,所以就採用 Local storge 的方式儲存密碼
十分不安全,只能當玩具 (´_ゝ`)
會做這個套件是源於先前在決定畢展主題的時候,原先有想走資安的方向
不是技術方面的,是想往使用者端
像是資訊外洩、密碼保護等等,將這類的資訊做有效傳播
但因為組員對資安不是很了解只有恐懼就因此作罷
不過也是因此開始關注「密碼安全」這件事情
也成了我做這個套件的契機
順帶一提,目前我是使用 1Password 來管理我的所有密碼
因為這篇文會以我這支套件作為說明案例
每種套件需要的架構不同,剛好我用了全部
所以我先簡單介紹一下大概涵蓋的功能
若沒有興趣的朋友,可以直接點選 Chrome Extension 架構 閱讀
範例功能簡介
基本上我的功能只有以下幾個:
- 產生密碼
- 儲存密碼
- 顯示密碼
- 填入密碼
- 刪除所有密碼(為了方便 Demo 所寫)
我的介面長這樣:
基本上我的介面都是偷懶用 Bootstrap 拉的 (〃∀〃)
透過規則選單和長度(預設 12 碼)產生符合條件的亂碼
我的設計是可以一直生產到自己滿意,按下 Confirm
才會存入
點選 All Password
可以看到哪個網址存了哪個密碼
對 我是用網址對應密碼的方式儲存,應該還要加帳號才對 QQ
點選 Fill in
可以直接找到填寫密碼的欄位,並找出這個網頁存的密碼填入
若先前沒有產生過或頁面上沒有可以填入密碼的欄位,會跳出提醒通知
下面是簡易的 Demo :
Chrome Extension 架構
要弄懂架構,對於第一次看文件開發的我來說是一大挑戰
但也都怪自己沒有耐心,不好好看文件
我可真兜了一大圈
或許藉由敘述這個過程可以讓大家更了解整個 Chrome Extension 的架構
當然,套件可不只我所講述的內容而已,詳細可以閱讀 官方文件
還是要自己按照需求去文件探索,規劃出合適的架構
只是剛好我使用了比較常用的架構罷了
首先,我原本是完全不知道套件是可以自己寫的
我就像是發現新大陸般,對套件一無所知
我前輩也只跟我說很簡單、不難
因此,起初我以為套件是這樣的:
什麼套件?
套件就跟寫網頁一樣,就是 HTML + CSS + JS 嘛
有什麼難的?
不就寫在網頁上?(゚⊿゚)
是的,當初的我如此天真
事後想起來,還真的不明白我怎麼會以為網頁跟套件適合在一起的?
根本沒有道理
因此我很快的發現不是這樣的,套件跟網頁是兩個不一樣的環境!
不僅如此他還有安裝檔
Manifest.json
什麼套件?
就多一個安裝檔難的倒我?
這不就寫了嗎 (゚⊿゚)
套件也和網頁一樣,只是多了一個安裝檔而已
安裝檔是最重要也是必不可少的文件,用於配置套件
除了描述套件基本資料外,還用於規範內容安全策略(CSP)、註冊腳本、管理權限…等
基本項目如下:
{
"manifest_version": 2,
"name": "My Extension",
"version": "versionString",
"browser_action": {
"default_title": ... ,
"default_icon": ... ,
"default_popup": "popup.html"
},
"content_security_policy": ... ,
"permissions" : [
...
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts" : [ {
"matches" : [ "https://*/*", "http://localhost/*"],
"js" : [ "contentScript.js" ]
} ],
}
其他項目可以參考 官網
- 內容安全策略(CSP)
Pop.html 可以引進 Pop.js ,但有個很重要的一點:
Pop.html 不可以使用 inline JS ,因此 Pop.js 勢必要用資源的方式引入 HTML 內
且遠端資源需要標註在 Manifest 內
"Content_sercurity_policy" : "script-src 'self' <外部資源連結> <外部資源連結> <外部資源連結>; object-src 'self' "
- 管理權限(permissions)
註冊使用的 Chrome API 以及activeTab
可 access 的 host - 註冊腳本
註冊使用的Background Script
或Content Script
以為套件如此爾爾的我
洋洋灑灑寫完密碼產生邏輯後,開始翻找文件找我有哪些 API 可以使用
終於選定了 localStorage 這支 API
好景不常,我遇到了 Bug,無法在 Pop.js 運行 localStorage API
於是我開始思索是不是我做錯了?
難道 Pop.js 沒辦法使用 Chrome API 嗎?
其實 Pop.js 就可以做到,是我當時菜自己沒 debug 好
不僅如此,還有我的填入功能!
網頁和套件是不同的世界
我根本獲取不了網頁的 DOM,這樣就無法找到密碼的欄位自動填字了
我到底該怎麼辦?
這時候的我意外的發現一個東西叫做 Content Script
Content Script & Background Script
Content Script 的我
什麼套件?
就多一個腳本就難的倒我?
這不就寫了嗎 (゚⊿゚)
天真的我以為我無法使用 localStorage API 肯定是因為我放錯位置了
於是興高采烈的試了一下,發現我成功儲存我的密碼
但這個密碼好像不是永久的,一下就消失了
這時候我開始陷入了沈思,奇怪我明明就存了為什麼還是不成功
我試了很多方法,換了很多關鍵字查詢(就是沒用心看手上的教學文和文件)但還是找不到原因
這時候我發現了一個東西叫做 Background Script
什麼套件?
就多一個腳本就難的倒我?
不知道這是在幹嘛,肯定是我把 Pop.js 放錯位置了
這不就寫了嗎 (゚⊿゚)
是的,我不假思索的把 Pop.js 註冊為 Background Script
然而沒有獲得任何幫助,我的密碼仍舊無法永久儲存
現在想起來真的是十分愚蠢的思路 (((゚Д゚;)))
當時的我在幹嘛?我是怎麼得出這個解法的?
由於當時錯誤的決定,自顧自地陷入迴圈
我躺在床上想了兩天兩夜
遊走在電腦與床鋪,絕望與更絕望之間
「不會吧 我該不會一輩子都寫不出來了吧」
對 大概就是這麼絕望
灰心喪志的我坐在電腦前不停下關鍵字
後來終於在一篇教學文發現自己的盲點
然而最悲傷的是,我早就找過這系列的教學文了,是我自己沒有認真的看手邊的資料
這兩天答案就在我身邊是我自己沒發現 ∑(ι´Дン)ノ
那時候真的很菜
那時,我才終於明白:
尼瑪,原來他們全都獨立存在啊?
是的, Background Script
、 Content Script
還有 Pop.js
是三種 Chrome Extension 不同情境會用到的 Script
Pop.js
:主要處理選單邏輯,只會在 彈出視窗運行 的時後才會被載入並且執行,可使用 Chrome APIBackground Script
:永久運行 的背景程式,通常用於監聽事件Background Script
與Pop.js
功能十分相近,可使用 Chrome API,也一樣無法與當前的網頁互動,他們差就差在 生命週期的長度
註冊為Background Script
程式可以在背景運作,也可在Manifest.json
註冊為非永久運行,因此被分為: persistent background pages & event pagesContent Script
:主要處理與 當前 網頁互動的行為,當前互動的行爲並不會與其他頁面共享,可操控頁面的 DOM 和 監聽事件,但無法使用所有 Chrome API ,註冊時須加上可 access 的 host
這時我才明白
原來我的密碼不會一直記著,是因為我放在 Content Script
而他只會儲存在當前的頁面,並不會共享啊!
因此我就按照我發現的環境,重新規劃了我的套件:
Pop.js
:主要處理頁面互動、密碼產生和監聽按鈕發送訊息給Background Script
和Content Script
Background Script
:主要儲存密碼和取出密碼回傳Pop.js
Content Script
:主要處理定位欄位、填入密碼
才寫成了現在 Demo 的樣子
但其實我是不用寫 Background Script
的,直接寫在 Pop.js
就好
畢竟我只是存、取,沒有需要長期運行在背景頁面
但當時太蠢沒有發現
所以,重新調整後,我的架構應該是這樣:
Pop.js
:主要處理頁面互動、密碼產生、儲存密碼和監聽按鈕發送訊息給Content Script
Content Script
:主要處理定位欄位、填入密碼
訊息傳輸
規劃好架構之後就可以開始實行了,但不妙的是:
上述講的這三種 Script 運行在不同的 Scope,要怎麼互相溝通呢?
就像我上面那張最終版的架構圖:
當 Pop.js
監聽到 Fill in
被點擊時,要如何通知 Content Script
找尋「可填入密碼的區塊」?
後來在 官網 上,我找到了訊息傳輸的方式,中間還一度誤會
會有誤會的情形是因為:Chrome API 提供了兩種傳送、一種接收方法
使三種作用域可以互相溝通:
chrome.runtime.sendMessage()
& chrome.tabs.sendMessage()
& chrome.runtime.onMessage.addListener()
Chrome 提供了統一的接收方法,卻有兩種傳送方式
他們並不能混用,是有各自的使用時機的
用於 對
Pop.js
&Background Script
傳送訊息時
chrome.runtime.sendMessage(string extension ID, any message, object options, function response Callback)
- string extension ID (optional) :可以向其他的擴充功能傳遞訊息,此時需附上另一個擴充功能的ID,如果省略ID,則消息只會在擴充功能內部傳送
ID 位於 chrome://extensions/
2. any message:以 JSON 的形式傳送 Ex. {greeting: “hello”},通關密語的概念,以確保是想要的人接收到
3. object options (optional):TLS 通道標識符是否會傳遞至onMessageExternal 事件
4. response Callback (optional) :接收到chrome.runtime.onMessage.addListener()
回傳的值後,執行的動作
用於 對
Content Script
傳送訊息時
chrome.tabs.sendMessage(integer tabId, any message, object options, function responseCallback)
- integer tabId:需附上tabID作為參數,讓Chrome知道他要傳送訊息的對像是哪個內容腳本,因此不同頁籤之間並不共享內容腳本,內容腳本在每個頁籤都會單獨注入
可透過chrome.tabs.query
取得當前tabID(tabs[0].id
)
chrome.tabs.query({ active: true, currentWindow: true },
function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, { greeting: "你好" },
function(response) {
console.log(response.farewell);
});
}
);
不管是使用 chrome.runtime.sendMessage()
還是chrome.tabs.sendMessage()
接收方法皆為
chrome.runtime.onMessage.addListener()
chrome.runtime.onMessage.addListener(
function(any message, MessageSender sender, function sendResponse)
{
sendResponse({result: "ok"});
...
}
)
- any message:
sendMessage()
傳來的 json 文字 - MessageSender sender:發送消息或請求的腳本對象
- function sendResponse:回傳 JSON 訊息給原請求對象的方法
sendResponse({result : 'ok'})
須盡快執行
先前我有試過做完判斷再回傳,但都會出現:Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
的錯誤訊息
了解上述的傳遞機制,我的架構就更完整了!
依照原本的設計,我資料傳輸的邏輯會長這樣:
但其實我的架構應該只需要這樣:
雖然舊版和新版都可以運行
但在資訊傳遞上,新版精簡許多!
費時兩週,我終於完成了簡易的密碼產生器 。・゚・(つд`゚)・゚・
其實成就感蠻大的!
而且可以做給自己使用,很有趣
蠻鼓勵大家都玩玩看
Chrome Extension 沒有到很複雜
當時的我也還是新手
僅利用下班和週末的時間,兩週就可以做成一支簡單的套件
且經過這次的訓練,我真的比較會看文件、對英文也更有耐心
三個月後的我再回頭看文件,已經沒有當時那麼吃力的感覺了
這次的文章比較特別,是我做完成品後的三個月才開始寫文章
因為想用故事的情境帶出整體架構
我花了很多時間在回想,當時的我在想什麼
其實這個套件是我在 前輩的課 上發表的結案作業
所以有做一份簡報記錄我當時的思路
在寫文章的時候,我真的很難想像為什麼我當時是這樣解決問題的
完全想不透
且我也發現了當初寫的漏洞: 根本不用 Background Script
順手也把檔案拿出來改了一下
但我沒有刻意去修正我文章的內容,反而是記錄這個錯誤
這篇本來就是給新手看,我想我的坑也可能是他人的坑
不如把我跌跌撞撞的過程寫出來
搞不好更能破解迷思
感謝大家把這篇看完
有任何問題或是有錯誤的地方都歡迎留言
以上
拍個手讓我知道,這個文章對你們有幫助 ♥(´∀` )人
可以拍五次喔!快來瘋狂擊掌!