[-GC-][-Fx-] 撰寫跨瀏覽器套件的注意事項

作者: danny0838 (道可道非常道)   2017-09-23 16:54:25
如題,Firefox 決定把套件系統改成和 Chrome 相差無幾的 WebExtension,
由於 API 相近,未來要同時開發 Firefox 和 Chrome 套件困難度會大大降低。
BUT! 美夢之中總是有這個 BUT...
Firefox 和 Chrome 提供的 API 從規格上到功能上畢竟還是有不少差異,
寫套件之初若沒有考慮到這些差異,
有天突然想移植到另一個瀏覽器時,
那程式碼改寫量之大,是可以殺死一隻程序猿的(?)
咱們現在就來談一談常見的雷區,
如果你打從一開始就要支援多瀏覽器,你一定得注意以下幾點;
如果你至少在未來不排除把套件移植到另一個瀏覽器,
那最好也稍微注意一下以下幾點,
一開始踩的雷越少,未來移植的痛苦就越少。
1. manifest.json
維護跨瀏覽器的套件有兩種策略,
一種是全部共用一套程式碼,裡面再寫一些條件判斷處理少數相容性問題;
另一種是某些地方準備兩套檔案,再寫個打包腳本包裝不同檔案給不同瀏覽器使用。
兩者各有優缺點,前者測試比較輕鬆,程式碼改了重新載入暫用套件就好,
缺點是有時會因為複雜的判斷條件造成效能變差,
還有就是有些 manifest 設定是不可能相容的,
如果你需要使用那些設定,就只能用後者的方法。
後者測試時比較麻煩,可能要常常重新打包安裝,
優點是效能會比較好,也能完全排除相容性問題。
我其中一個專案很不幸地必須使用無法相容的 manifest,
我是準備 manifest.json 和 manifest-firefox.json 兩個檔案,
然後寫個打包腳本,在包 Firefox 版時用 manifest-firefox 取代 manifest。
平時主要以 Chrome 測試,偶爾需要在 Firefox 上測試時就打包裝進去,
如果需要比較頻繁測試就暫時把 manifest-firefox 改成 manifest 讀暫用套件。
1.1. "applications"
只有 Firefox 支援 "applications",而 Chrome 不認得。
如果 manifest.json 寫了這項,
在 Chrome 載入暫用套件時會跳出礙眼的警示訊息說有不認得的參數,
不過實際打包安裝時不會出現警示訊息。(Firefox 遇到不認得的參數也一樣)
當然也可以選擇不寫,不過不寫會導致一些麻煩,
比如因為沒指定 ID 導致載入暫用套件時無法使用 storage,
或者上傳 AMO 時手殘上傳成另一個套件,之後就不能上傳同一個版本號了XD
1.2. "version" & "version_name"
Chrome 的 "version" 只能使用數字和小數點,不過另外支援 "version_name";
Firefox 支援比較多種版本號格式,但不支援 "version_name"。
可參見: http://j.mp/2yw4hSp
如果你想在 Firefox 和 Chrome 上釋出 "1.1.3a1",
那麼你不得不準備兩個 manifest.json。
因為 Firefox 必須寫 "version": "1.1.3a1",而 Chrome 不接受且無法安裝。
1.3. "browser_action" & "page_action"
Chrome 不能同時指定 browser_action 和 page_action,否則會出錯無法安裝。
Firefox 則可以同時指定 browser_action 和 page_action。
什麼時候需要同時支援兩者呢?
我遇到的一個情況是 Firefox Android < 55 版不支援 browserAction,
且由於 Android 的 pageAction 是所有分頁共用,
因此要支援舊版 Android 可考慮用 pageAction 替代 browserAction。
也就是 manifest 填入 browser_action 和 page_action,指定為同樣頁面,
然後偵測不支援 browserAction 的版本顯示 pageAction 作為 fallback。
當然另一個做法是只支援 Firefox >= 55 版,
不過 Firefox 55 版才出來不久,而且有些老用戶還在死守 Firefox 52 ESR,
這麼做會流失一些潛在使用者。
或者你也可以只用 browserAction,聲稱支援 Firefox >= 45 版,
然後在 Firefox Android < 55 版的使用者發現少個按鈕時管他去死XD
不過 Firefox Android 使用者應該比較少死守舊版的... (應該...)
而且隨著版本往上爬,以後這問題應該會逐漸減少到可忽略。
當然也可能你就是想要在 Firefox 上兩個按鈕都有,
那就乖乖準備兩套 manifest 吧。
2. chrome vs browser
Chrome 的套件 API 只支援 chrome.*。
Firefox 主要支援 browser.* ,也相容支援 chrome.*。
BUT... 程序猿的猿生總是有這個 BUT...
Firefox 有時使用 browser.* 和 chrome.* 的行為是不同的。
其一是 browser.* 大多支援傳回 Promise,也相容支援傳入 callback 函數;
而 chrome.* 不支援傳回 Promise。
其二是 Firefox 專屬的 API 只能使用 browser.* 呼叫,
例如 chrome.runtime.getBrowserInfo() 會傳回 undefined。
邪惡的是 chrome.runtime.getBrowserInfo 會傳回 function 而非 undefined,
如果你偵測 if (chrome.runtime.getBrowserInfo) { ... } 就中計了XD
其中的基本概念很清楚:
使用 chrome.* 表示在 Chrome 和 Firefox 都能跑,
使用 browser.* 就只能在 Firefox 上跑。
在撰寫跨 Firefox 及 Chrome 的瀏覽器套件時,
我個人是建議原則上全部使用 chrome.*,
只在特定場合搭配瀏覽器偵測和 browser.* 做相容性處理。
另一個做法是用 browser.* 然後在 Chrome 版本加上 polyfill,
Mozilla 有提供在 Chrome 補上 browser.* 的腳本,
不過需要先安裝 node.js 再跑 npm 安裝及編譯,
而且不曉得是不是人品問題,我實際使用總是會發生一些不明的錯誤,
那個 polyfill 本身程式碼頗複雜,我最後是直接放棄了XD
總之,如果你打算用 browser.* 又打算支援 Chrome,先仔細測試過比較保險...
有興趣的人可以去這裡找來試試: https://j.mp/2fGZYvN
再不然就是自已針對用到的 API 寫 polyfill...
3. 無痕/隱私視窗支援
Chrome 有一個 "incognito" manifest 參數決定如何支援無痕視窗,
可參見 https://j.mp/2xZf3Dz
大略而言有三種模式:
"spanning" (預設) 模式是這套件在所有 Chrome 視窗共用一個 process,
但無痕視窗不共用這個 process,因此該套件的頁面不能在無痕視窗中載入;
"split" 模式是這套件在一般視窗共用一個 process 而在無痕視窗共用另一個,
而兩者完全不互通,比如這套件不能從一般視窗在無痕視窗開分頁或反之;
"not_allowed" 則是完全不允許在無痕模式執行。
我個人經驗是 spanning 模式比較容易出問題,
比如無痕視窗無法正常下載東西等等,
反而 split 運作得比較像正常情形,
不曉得為什麼以 spanning 作為預設值,
不過這可能和我寫的套件性質有關就是了。
Firefox 不支援 "incognito" 參數,不過其運作方式和 Chrome 又不太一樣,
基本上是在所有 Firefox 視窗共用一個 process,
但又不像 Chrome 禁止在無痕視窗載入套件的頁面。
不過 Firefox 有其他限制,
比如隱私視窗 content script 跑 runtime.getBackgroundPage 一律傳回 null,
理由是背景頁面跑在一般視窗,而隱私視窗不允許和一般視窗直接溝通。
如果你想支援隱私模式又有需要讓 browser action 和背景頁面溝通,
相容性最好的做法是忘記 getBackgroundPage 並且努力練各種 messaging。
方法簡而言之就是 browserAction 頁面和 content script 一樣
可以用 chrome.runtime.sendMessage 和背景頁面溝通,
無論是在 Firefox 或在 Chrome,剩下就 RTFM 囉。
除此之外還要注意儲存資料的問題。
對於 Chrome,
無痕視窗儲存的 indexedDB 或 localStorage 資料和一般視窗不共用,
並且無痕視窗一關就全部洗掉,和 cookie 等東西是一樣的。
對於 Firefox,
隱私視窗完全不支援 indexedDB (https://mzl.la/2xZ2GqW),
至於 localStorage 我研究比較少,可參見 https://j.mp/2xvbFyF。
大體而言,要在套件儲存資料且在一般與無痕/隱私視窗間互通,
storage.local 和 storage.sync 才是最可靠的做法。
簡而言之,隱私/無痕視窗支援是寫套件的大雷區,
有空稍微關切一下,套件寫好了也記得在隱私/無痕視窗測試一下,
否則有天要改會欲哭無淚...
4. 其他
其他還有一些大大小小的雷區,
比方說 Firefox 套件背景頁面不能 alert()(被很多人罵翻的設定);
其他如 Chrome 套件頁面產生的 blob URL 會視為一般頁面,
而 Firefox 會視為套件頁面(可呼叫套件 API、有 CSP 限制)等等。
更多哩哩叩叩的可參考: https://j.mp/2fGZYvN
或者自己玩一玩踩到雷後分享給大家長知識XDDD
以上,祝大家好運,也歡迎分享跨(?)的踩雷經驗XDD
作者: eight0 (欸XD)   2017-09-23 17:21:00
1.3 in-my-pocket 就使用了兩者2. 用 Promise 在 background/content script 交換資料時比較方便,可以方便的處理錯誤訊息
作者: Kenqr (function(){})()   2017-09-23 17:31:00
作者: toploader   2017-09-23 18:49:00
管他去死XD我比較建議可以直接寫明,以Firefox 52 ESR為例,無法升級的可能原因之一在於XP,所以會逐漸有更多套件無法使用,是做出這個選擇的使用者本就可以預期、接受的只能在裝了之後才發現要用的不能用,不太好
作者: number543 (sea)   2017-09-23 20:41:00
作者: t7yang (t7: 我認為這是一種背叛)   2017-09-23 21:02:00
大大加入社團拯救眾生吧(笑https://www.facebook.com/groups/2007579976142182/
作者: karst10607 (誰可以協助我開板?)   2017-09-23 21:11:00
大大幫忙教我們怎麼把deluminate port到Fx吧 qqFx nightly 真的爆快又滑順 爽度比GC系的高很多...
作者: abc0922001 (中士abc)   2017-09-23 22:32:00
感謝,這邊加到書籤
作者: hdd60311 (太空戰鬥機漁師一枚)   2017-09-23 22:47:00
我只求有人能做WE版BBS客戶端,現在只差這個了
作者: Kenqr (function(){})()   2017-09-23 23:18:00
BBS client +1
作者: hdd60311 (太空戰鬥機漁師一枚)   2017-09-24 00:26:00
能上PTT就夠了,目前那個websocket實驗客戶端用起來感覺不是很順,而且也不知道還有沒有在開發?
作者: Kreen (每天要更優秀一點)   2017-09-24 00:36:00
這邊可以許願嗎?希望 Distill Web Monitor鄏댠webextension,是用來監視網頁變更通知XD
作者: hdd60311 (太空戰鬥機漁師一枚)   2017-09-24 00:52:00
https://goo.gl/xLdQ3P 不用許已經有惹
作者: Kreen (每天要更優秀一點)   2017-09-24 01:27:00
原來已經有了,感恩
作者: Kenqr (function(){})()   2017-09-24 15:53:00
chrome 有打算以後會支援 browser.* 的 API 嗎?
作者: t7yang (t7: 我認為這是一種背叛)   2017-09-24 16:05:00
目前幾個瀏覽器廠商在推動web-ext成為標準,成功的話,到時候介面應該會比較統一。
作者: hijkxyzuw (i,j,k) ×(x,y,z)   2017-09-24 23:43:00
好強
作者: goldie (阿良)   2017-09-25 13:58:00
推!
作者: woow1225 (B.N)   2017-09-25 18:42:00
推 雖然沒看完XD
作者: stucode   2017-09-26 06:07:00
推熱心教學。
作者: kiki1503 (琦琦壹伍零參)   2017-09-27 00:05:00

Links booklink

Contact Us: admin [ a t ] ucptt.com