今天是 10/1,想必有不少人已經完賽了吧! ^_^
而我這個最後一刻才開賽的人,
才剛衝破第 15 天,正準備跨進下半場呢!
這次參加鐵人賽,主要是以完賽為目標。
過程邊學邊寫,一路上也學到不少東西。
比如今天所要談的主題,
也是我這兩天才發現的好物。
哎。說起來都是淚。
技術人真是很辛苦,一直要很勤勞很用功才行,
否則稍不留意,就錯過好用好玩的新事物了!
話說前兩天完成了「一鍵翻譯」的快速功能,
其中需要使用者輸入 API_KEY 的部分,
我們雖然建立了一個輸入界面,
但每次重新載入外掛,都要重新把 API_KEY 複製貼上。
哦不,這又再次違反 DRY 的原則了。。。
好吧好吧。
我把 API_KEY 保存到【背景頁面】的 localStorage 裡頭,
這樣總可以了吧。
咦咦咦?!【背景服務】怎麼沒辦法使用 localStorage 呢?
我趕緊到網頁的【原始內容】和【彈出頁面】裡測試一下:
【原始內容】和【彈出頁面】都沒問題,可以順利使用 localStorage。
為什麼只有【背景服務】無法使用呢?
抱著萬事問 Google 的精神,我上下找了一輪,
才發現原來是要在 manifest.json 設定權限:
"permissions": [
"storage"
]
問題是,做好這個設定之後,
【背景服務】還是不能用 localStorage 呀!
再仔細一看,才發現原來能用的其實是 chrome.storage 呀!
這個 chrome.storage,又是什麼東西呢?
官方網頁這麼說道:
嗯嗯嗯。看起來蠻好用的。。。
我們就先拿 API_KEY 來做實驗好了。
首先,我們可以在 popup.js 裡,
把 API_KEY 保存到 chrome.storage.local 中:
// 點擊【送出】按鍵的話...
// 在輸入框按下【Enter】的話...
...
chrome.storage.local.set({api_key: api_key}, showAPI_KEYOnPopup);
...
後面用了一個叫做 showAPI_KEYOnPopup 的 callback 回調函式,
它會在保存好資料之後才執行。
這是我們所獨立出來的一個函式,主要負責的工作是,
根據 chrome.storage.local 裡 api_key 的值,
在【彈出頁面】做出適當的呈現。
由於這個工作會一再重複執行,
所以我們特別把它獨立出來:
function showAPI_KEYOnPopup() {
// 取值
chrome.storage.local.get('api_key', r => {
// 根據所取得的值,做出適當的呈現
...
})
}
API_KEY 主要是給【背景服務】向外連線使用,
但這裡完全不用透過 sendMessage 的機制,與【背景服務】進行溝通,
因為 background.js 也可以從 chrome.storage.local,
直接取得 API_KEY 的值:
chrome.storage.local.get('api_key', r => {
if (r.api_key) {
// 注意!Promise 裡的 Promise。。。Promise chain...
return getGoogleTranslationV2(r.api_key, sent_html)
.then((tran_sent_html)=> {
chrome.tabs.sendMessage(
sender.tab.id,
{
cmd: 'tran_sent_html',
data: {tran_sent_html: tran_sent_html}
}
);
});
...
}
...
}
這裡先用 chrome.storage.local.get(’api_key‘, r =>{...}) 取值,
再用 getGoogleTranslationV2(...) 向外部取得譯文,
最後再利用 sendMessage 的機制,把譯文送往【原始內容】,
以便更新頁面裡的每個句子。
要特別提醒的是,
chrome.storage.local.get() 本身回應的是一個 Promise,
getGoogleTranslationV2() 本身回應的也是一個 Promise,
這樣一來,就會出現一個 Pormise 包著另一個 Promise、
或者說一個 Pormise 執行完才能再執行另一個 Promise 的情況。
在程式碼的寫法上要稍微留意一下喲。
只要靠著這些程式碼,【背景服務】就能順利存取 API_KEY 了。
由於少了 sendMessage 的來回溝通,程式碼果然簡潔不少。
這就是採用 chrome.storage 的一大好處。
如果是用 sendMessage 的機制來實現同樣的效果,
來來回回的感覺就好像在踢皮球,
一下子踢過來一下子踢過去,
總感覺不夠簡潔、不夠俐落。
但從另一個角度來看,
【背景服務】保存在 chrome.storage 裡的資料,
content.js 和 popup.js 都能輕鬆存取、修改、刪除,
感覺上好像也有點危險呀。
還好的是,如果在【原始內容】按下【F12】來到後台,
就會發現在 console 裡並不能使用 chrome.storage 相關的指令。
這樣的限制,讓人稍稍放心了一點。
不過,如果打開【彈出頁面】或【背景服務】的後台,
只要輸入 chrome.storage.local.get(),就可以看到所保存的資料了。
因此,對於此種保存方式,還是不能完全放心。
如果有必要的話,各位還是可以利用我們所提供的【清除】按鍵,
清除掉保存在 chrome.storage 裡的 API_KEY 喲。
chrome.storage 的功能還不只如此。
如果用 chrome.storage.sync 來取代 chrome.storage.local,
甚至還可以用上 Chrome sync 的同步功能。
如果採用這種方式保存各網頁的譯文,
我們的翻譯就能輕鬆在不同裝置上使用了。
這聽起來真的滿酷炫的!
不過,隨著翻譯資料逐漸增加,
對於儲存空間的需求也會隨之上升。
chrome.storage 也有 5MB 的容量限制,
雖然可以手動增加容量,
但我們還是應該先考慮如何精簡資料,
提高儲存空間的使用效率。
之後我們也會再討論這個主題。
我來說說個人對於 chrome.storage 的心得好了。
如果說的不對,還請多多包涵。
我覺得 chrome.storage 就像是【背景服務】專屬的資料保存方式。
也許有人會說,
【原始內容】、【彈出頁面】也可以存取 chrome.storage 呀!
但其實大家存取到的內容都是相同的。
我們可以把【背景服務】視為是一個【在本地執行的服務主機】,
搭配提供了 chrome.storage 這個資料保存服務。
【原始內容】與【彈出頁面】只是有權限可以去使用這個服務而已。
那我們為什麼不說,chrome.storage 是一個獨立的服務,
【背景服務】其實和【原始內容】、【彈出頁面】一樣,
都只是它的使用端而已?
這是因為 chrome.storage 與【背景服務】是綁在一起的。
不同的外掛,就有不同的【背景服務】,對應到不同的 chrome.storage。
如果我們使用 chrome.storage.sync,
Chrome 進行同步時,雲端也會針對每一個不同的【背景服務】,
同步保存一份相應的 chrome.storage 資料。
也就是說,每個【背景服務】都會搭配一份獨立的 chrome.storage,
所以我會把【背景服務】與 chrome.storage 視為一體,
將 chrome.storage 看成是【背景服務】的專屬資料儲存方式。
就像每個外掛的【彈出頁面】,都有自己專屬的 localStorage,
每個網域的【原始內容】,也都有自己專屬的 localStorage 一樣。
這樣的表達方式,不知道夠不夠清楚。
也許有機會的話,還可以用另一種更清楚的方式來描述。
不過今天就先這樣吧。 ^_^
另外,除了可以在【彈出頁面】中設定 API_KEY 之外,
其實瀏覽器外掛還提供了另一種途徑:options ui,
讓我們可以針對外掛,保存相關的設定選項。
其做法相當簡單,
所需要的程式碼我們也幾乎都完成了,
所以這裡就簡單介紹一下好了。
首先要在 manifest.json 中進行設定。
設定方式有兩種,一種是採用獨立的頁面:
...
"options_page": "popup/popup.html",
...
另一種則是採用嵌入式的畫面:
...
"options_ui": {
"page": "popup/popup.html",
"open_in_tab": false
},
...
兩種做法都是可行的,
我個人則是比較喜歡嵌入式畫面的做法。
咦?這樣就完成了喲。
接著只要在外掛圖標按下滑鼠右鍵,
選擇其中的【選項】,就可以看到我們的選項設定畫面了。
各位可以看到,
我直接用【彈出頁面】的 popup.html,
(裡頭會帶入 popup.js 和 popup.css)
來做為選項設定畫面。
畢竟我們只有一個選項(API_KEY)需要設定,
而且也已經把相應的處理方式全都寫好了。
如果你想用另一個 html 檔案來呈現選項畫面,當然也可以囉。
這個部分就不多談,有興趣你就自己來吧! ^_^