最近思うところあってブラウザー拡張を作って公開しました。(Macが対応していない絵文字を使っているので、変に見えます。いずれどうにかする。) 仕事中についSNS見ちゃうのを止めるやつです。
- Chrome → Stop SNS – Chrome Web Store
- Firefox →Stop SNS – Add-ons for Firefox
- Edge → 未公開
- ソース → ginpei/stop-sns: Chrome, Edge, Firefox extension that helps you to spend less time on SNS.
作ったものについてはそのうち記事に書きたいと思ってます。あとブラウザー拡張の作り方についてもちゃんとした形にしたいなと思って準備中。
それはそれとして、作成中に得た知見のひとつ、互換性についてです。
先にまとめ
- ポリフィル使えばすぐコード共通化できるしPromise化する
- 共通化したコードはFirefoxに寄る
- Edgeはもうひと手間
async
/await
はいいぞ
互換性
前提として、ブラウザー搭載のAPIは以下のような感じ。
- Chromeは
chrome
オブジェクト以下にAPIを持つ - Firefoxは
chrome
、Edgeはbrowser
にChrome互換APIを持つ - Chrome互換APIは、コールバックは引数に与える
Firefoxは互換APIに加えて別にAPIがあります。
- Firefoxは
browser
にChromeのAPIと同機能だが別I/FのAPIを持つ - Firefox独自APIはPromise化されている
async
/await
を利用できる- 後述のポリフィルで、ChromeとEdgeでもいける(いえーい)
つまりどのブラウザーでもだいたい同じ機能がそろってるけど、インターフェイスがちょっと違うよと。表にまとめるとこんな感じ。
ブラウザー | chrome |
browser |
ポリフィル |
---|---|---|---|
Chrome | ◎ | ✘ | 〇 |
Edge | ✘ | △ | 〇 |
Firefox | 〇 | ◎ | – |
Promiseだと嬉しいって話
「ちょっと違うインターフェイス」の差はPromise化されているかどうかです。なので共通化する場合は、全部Promise化するか、逆に全部Promiseを剥がすかのどちらかになるわけですが、前者Promise化する方をお勧めします。というわけでその理由なんですけれども。
コールバック関数方式
まずこちらの例。Chrome互換APIで、保存した情報を持ってくるやつです。
chrome.storage.local.get(["item1", "item2"], (result) => { console.log('# result', result.item1, result.item2); });
まあこれはこれで問題ないんだけど。
Promise方式
一方FirefoxがChrome互換APIとは別に独自に持っている browser
系APIだと、これがPromiseになる。(「にもなる」の方が正しいかも。)
browser.storage.local.get(["item1", "item2"]).then((result) => { console.log('# result', result.item1, result.item2); });
これだけだと、まああんまり変わらないように見える。
いちおうメリットとして、Promiseを使うと非同期処理を連結してもコードのインデントが深くならないというものががあります。が、そんなものより、ES2017で導入された async
/await
を利用できる面が大きい。
async
/await
前述のPromiseを使ったコードは、( async
な関数の中で)以下のように書ける。
const result = await browser.storage.local.get(["item1", "item2"]); console.log('# result', result.item1, result.item2);
まっすぐ書けるのでたいそう見やすい。
どうすか
Promiseの方が良くないすか?
APIをPromise化するライブラリ
というわけでPromise化したくなりましたか? なりましたね? なったので、これ↓を導入して実現します。
残念ながらライブラリのファイルが単体で公開されていないようです。ビルドシステムを導入していない場合でも、一度npmでインストールしてからファイルをコピーしてきます。
$ npm install webextension-polyfill $ cp node_modules/webextension-polyfill/dist/browser-polyfill.min.js .
でもって読み込むようにする。
<script src="/browser-polyfill.min.js"></script> <script src="/popup.js"></script>
これでChromeでも browser
を使って、 async
/await
記法でさらさら書けるようになります。やったー!
Edge対応
最終的にできあがったのはこれ↓
ライブラリを有効化する
件のライブラリは現状ではEdgeに対応しておらず、検討中みたい。
詳細省略するけど、ライブラリ読み込み前にこれ↓を実行したらEdgeもうまいこといった。
// ※別途に `cloneDeep()` 的なものをご用意ください "use strict"; // "SCRIPT5045: Assignment to read-only properties is not allowed in strict mode" を避ける try { if (window.browser) { browser.storage.local.get = browser.storage.local.get; } } catch (error) { // 上書きできるようにする window.chrome = cloneDeep(window.browser); // Chrome偽装 window.browser = undefined; }
openOptionsPage()
他に、自分の見つけた範囲だと chrome.runtime.openOptionsPage()
が実装されていないようなので、自前でpolyfillを実装した。他にもそういうのありそう。
window.chrome.runtime.openOptionsPage = () => { const { options_page } = browser.runtime.getManifest(); browser.tabs.create({ url: `/${options_page}`, }); };
manifest.json
の互換性
今回特につまづかなかったけど、いちおうブラウザーによって必須だったり名前が違ったりするところがある。
一覧
options_ui
Chromeでは chrome_style: true
が推奨。Edgeでは options_page
になる。
{ … "options_page": "options_ui/index.html", "options_ui": { "page": "options_ui/index.html", "chrome_style": true } }
あとFirefoxでも browser_style: true
が推奨だけど、初期値がそうなっているので記述しなくてもいい。
APIの互換性
ライブラリがあるとは言ってももともと実装されていないものはされていないのであきらめる。
MDNの互換性の表を見るのが良さそう。
例えば先ほどの runtime.openOptions()
は、やっぱり対応してなかった。
その他
- Firefoxの
chrome
オブジェクトについての記述がMDNで見当たらなかった - Firefoxの
browser
でもChrome互換APIと同様にコールバックを引数に与えられるっぽい。でもMDNでの記述は見当たらなかった - Edgeは全く関係ない
chrome
オブジェクトを持っているっぽいけど詳細不明 - ブラウザー拡張の標準化がW3Cで進行中 → Browser Extensions
- 標準化されたAPIはPromise化されている(つまりFirefox風)
- Chromeは標準APIに寄せる気がないらしい[要出典]
- EdgeはChromeだけを見ているらしい[要出典]
- Edge拡張を作るのは簡単だけど、公開するの難しそうでつらい
おしまい
async
/await
いいわー。
更新履歴
2018-04-24
- Edge対応コードの
"use strict"
がなんか抜けてたのを修正 - Edge対応コード完成版のリンクを追加
- APIの互換性について追加