最近思うところあってブラウザー拡張を作って公開しました。仕事中につい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だと嬉しいって話
まずこちらの例。Chrome互換APIで、保存した情報を持ってくるやつです。
1 2 3 | chrome.storage.local.get([ "item1" , "item2" ], (result) => { console.log( '# result' , result.item1, result.item2); }); |
まあこれはこれで問題ないんだけど。
一方FirefoxがChrome互換APIとは別に独自に持っている browser
系APIだと、これがPromiseになる。(「にもなる」の方が正しいかも。)
1 2 3 | browser.storage.local.get([ "item1" , "item2" ]).then((result) => { console.log( '# result' , result.item1, result.item2); }); |
これだと、まああんまり変わらないように見える。
Promiseを使うと非同期処理を連結してもコードのインデントが深くならないというメリットがある。が、そんなものより、ES2017で導入された async
/await
を利用できる面が大きい。
上記のPromiseを使ったコードは、( async
な関数の中で)以下のように書ける。
1 2 | const result = await browser.storage.local.get([ "item1" , "item2" ]); console.log( '# result' , result.item1, result.item2); |
まっすぐ書けるのでたいそう見やすい。
どうすか
Promiseの方が良くないすか?
APIをPromise化するライブラリ
というわけでPromise化したくなりましたか? なりましたね? なったので、これ↓を導入して実現します。
残念ながらライブラリのファイルが単体で公開されていないようです。ビルドシステムを導入していない場合でも、一度npmでインストールしてからファイルをコピーしてきます。
1 2 | $ npm install webextension-polyfill $ cp node_modules /webextension-polyfill/dist/browser-polyfill .min.js . |
でもって読み込むようにする。
1 2 | < script src = "/browser-polyfill.min.js" ></ script > < script src = "/popup.js" ></ script > |
これでChromeでも browser
を使って、 async
/await
記法でさらさら書けるようになります。やったー!
Edge対応
件のライブラリは現状ではEdgeに対応しておらず、検討中みたい。
詳細省略するけど、ライブラリ読み込み前にこれ↓を実行したらEdgeもうまいこといった。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // ※別途に `cloneDeep()` 的なものをご用意ください // "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を実装した。他にもそういうのありそう。
1 2 3 4 5 6 | 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
になる。
1 2 3 4 5 6 7 8 | { … "options_page" : "options_ui/index.html" , "options_ui" : { "page" : "options_ui/index.html" , "chrome_style" : true } } |
あとFirefoxでも browser_style: true
が推奨だけど、初期値がそうなっているので記述しなくてもいい。
その他
- Firefoxの
chrome
オブジェクトについての記述がMDNで見当たらなかった - Firefoxの
browser
でもChrome互換APIと同様にコールバックを引数に与えられるっぽい。でもMDNでの記述は見当たらなかった - Edgeは全く関係ない
chrome
オブジェクトを持っているっぽいけど詳細不明 - ブラウザー拡張の標準化がW3Cで進行中 → Browser Extensions
- 標準化されたAPIはPromise化されている(つまりFirefox風)
- Chromeは標準APIに寄せる気がないらしい[要出典]
- EdgeはChromeだけを見ているらしい[要出典]
おしまい
async
/await
いいわー。