如题,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