File System Access API と拡張機能の相性はいまいちだった
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 2
◆ File System Access API の機能はページを開くたびにファイルを選択する必要あり
◆ 拡張機能でバックグラウンドでハンドルを保持すればブラウザを再起動するまで再選択不要
◆ バックグラウンドでもファイルを開くダイアログを出してハンドルを取得できた
◆ 書き込みをしようとするとパーミッション取得のためのプロンプトが出ない
◆ ページとして開いてないバックグラウンドスクリプトだと出せないみたい
◆ 拡張機能から file スキームの読み込みだけならこれまでもできたのであまり嬉しくない
◆ 拡張機能でバックグラウンドでハンドルを保持すればブラウザを再起動するまで再選択不要
◆ バックグラウンドでもファイルを開くダイアログを出してハンドルを取得できた
◆ 書き込みをしようとするとパーミッション取得のためのプロンプトが出ない
◆ ページとして開いてないバックグラウンドスクリプトだと出せないみたい
◆ 拡張機能から file スキームの読み込みだけならこれまでもできたのであまり嬉しくない
Chrome86 で追加された File System Access API でブラウザからローカルのファイルを読み書きできるようになりました
便利そうと思っていたものの特に使ってなくて 今回 拡張機能で使うと良いかもと思いついて使ってみました
ブラウザに操作を許可するフォルダやファイルをユーザに選択してもらう必要があります
「<input type="file">」みたいにダイアログが出て ユーザが選択すると FileSystemFileHandle または FileSystemDirectoryHandle のオブジェクトを取得できます
このハンドルから File オブジェクトを取得してあとは input のときと同じように FileReader や .text() メソッドなどで File の中身を取得できます
このハンドルオブジェクトはサーバや localStorage などには保存できないので ページを開くたびにユーザに選択してもらわないといけません
これがすごく不便で使い道が限られるところです
拡張機能で扱う設定やデータを localStorage などではなく OS 側から読み書きできる JSON ファイルで管理したいと思うことはあるので そういうことができると助かります
これまでもできなくはなかったのですが ブラウザ外のローカルアプリを作成して native messaging を使って通信するという扱いづらいもので 設定やデータをファイルシステムのファイルで管理したいというだけで使おうとは思えないものでした
同じ拡張機能の中でも ポップアップページや独立した拡張機能のページやバックグラウンドスクリプトでそれぞれ別ページです
バックグラウンドで動き続けるのはバックグラウンドスクリプトのみです
ポップアップページや拡張機能のページは ページを閉じれば変数上のハンドルは保持できません
拡張機能内での通信でもやりとりは JSON 化できるオブジェクトでのやりとりなので ハンドルは渡せません
そうなると バックグラウンドスクリプトでファイルを開く必要が出てきます
バックグラウンドスクリプトでダイアログを表示できるのか不安でしたが やってみると問題なく開けました
ただ ユーザ操作のタイミングでしか実行できないという制限は拡張機能でもありました
バックグラウンドスクリプトのページにユーザ操作できないのでは?とも思ったのですが バックグラウンドスクリプトのページでユーザがなにかする必要はありませんでした
ポップアップページなどにボタンを用意し 押されたら拡張機能内でメッセージを送り バックグラウンドスクリプトでファイルを開くダイアログを出す関数を呼び出せば動きました
プログラム的にはこういう感じです
[popup.js]
[background.js]
これでポップアップページのボタンを押すと console にファイルの中身が表示されます
バックグラウンドスクリプトの変数にハンドルを保持しているので ポップアップを開き直してボタンを押しても ファイルを選択し直すことなくファイルの中身を受け取れます
しかし……
書き込みを試してみると 書き込みはできませんでした
書き込みするにはパーミッションを得るための確認プロンプトが出てきて ユーザが許可する必要がありますが この確認プロンプトが出てきません
下のコードで requestPermission を実行しても何も起きず "prompt" が返ってきます
いろいろ試してみると 拡張機能だからというわけではなく 実際に開いているページじゃないからのようです
バックグラウンドスクリプトやポップアップページでは確認プロンプトが出ませんでしたが ポップアップページの HTML をタブでページとして開くと確認プロンプトが出ました
バックグラウンドスクリプトでは ファイルを開くダイアログは出るものの確認プロンプトが出せず 読み込みのみという扱いにしかできませんでした
のように file スキームを許可して file スキームにアクセスすればダイアログなしでアクセスできます
fetch は file スキームをサポートしてないので XHR を使います
file スキーム内のパーミッションを求める拡張機能は怪しいので セキュリティ面では開くたびにユーザが選択したファイルしかアクセスできない File System Access API のほうが安心できそうです
ただ 基本自分用で file スキームも全許可求めて許可する使い方をしてると File System Access API の恩恵がなにもないんですよね
ページとして開いていなくても書き込みパーミッションの確認プロンプトが出るようになってほしいものです
postMessage でも送れるんだとか
ということは拡張機能内の sendMessage でも送れる?と思って試してみました
しかし 結果は残念ながら送れませんでした
受け取った側では空のオブジェクトとして受け取っていました
拡張機能関係の対応は後回しみたいですね
便利そうと思っていたものの特に使ってなくて 今回 拡張機能で使うと良いかもと思いついて使ってみました
File System Access API の不便なところ
ブラウザからローカルファイルを操作できると言ってもセキュリティの都合で自由度は高くありませんブラウザに操作を許可するフォルダやファイルをユーザに選択してもらう必要があります
「<input type="file">」みたいにダイアログが出て ユーザが選択すると FileSystemFileHandle または FileSystemDirectoryHandle のオブジェクトを取得できます
このハンドルから File オブジェクトを取得してあとは input のときと同じように FileReader や .text() メソッドなどで File の中身を取得できます
このハンドルオブジェクトはサーバや localStorage などには保存できないので ページを開くたびにユーザに選択してもらわないといけません
これがすごく不便で使い道が限られるところです
拡張機能なら
拡張機能であればずっとバックグラウンドで起動しているので この制限は受けなそうです拡張機能で扱う設定やデータを localStorage などではなく OS 側から読み書きできる JSON ファイルで管理したいと思うことはあるので そういうことができると助かります
これまでもできなくはなかったのですが ブラウザ外のローカルアプリを作成して native messaging を使って通信するという扱いづらいもので 設定やデータをファイルシステムのファイルで管理したいというだけで使おうとは思えないものでした
同じ拡張機能の中でも ポップアップページや独立した拡張機能のページやバックグラウンドスクリプトでそれぞれ別ページです
バックグラウンドで動き続けるのはバックグラウンドスクリプトのみです
ポップアップページや拡張機能のページは ページを閉じれば変数上のハンドルは保持できません
拡張機能内での通信でもやりとりは JSON 化できるオブジェクトでのやりとりなので ハンドルは渡せません
そうなると バックグラウンドスクリプトでファイルを開く必要が出てきます
バックグラウンドスクリプトでダイアログを表示できるのか不安でしたが やってみると問題なく開けました
ただ ユーザ操作のタイミングでしか実行できないという制限は拡張機能でもありました
バックグラウンドスクリプトのページにユーザ操作できないのでは?とも思ったのですが バックグラウンドスクリプトのページでユーザがなにかする必要はありませんでした
ポップアップページなどにボタンを用意し 押されたら拡張機能内でメッセージを送り バックグラウンドスクリプトでファイルを開くダイアログを出す関数を呼び出せば動きました
プログラム的にはこういう感じです
[popup.js]
document.querySelector("button").onclick = () => {
chrome.runtime.sendMessage({ req: "open-file" }, body => {
console.log(body)
})
}
[background.js]
let handle
const openFile = async () => {
if (!handle) {
const handles = await showOpenFilePicker()
handle = handles[0]
}
const file = await handle.getFile()
const body = await file.text()
return body
}
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.req === "open-file") {
openFile().then(body => sendResponse(body))
return true
}
})
これでポップアップページのボタンを押すと console にファイルの中身が表示されます
バックグラウンドスクリプトの変数にハンドルを保持しているので ポップアップを開き直してボタンを押しても ファイルを選択し直すことなくファイルの中身を受け取れます
バックグラウンドスクリプトでの制限
ブラウザ再起動時は再選択になるものの これでファイルシステム側に設定などを保存するようにできると思ってましたしかし……
書き込みを試してみると 書き込みはできませんでした
書き込みするにはパーミッションを得るための確認プロンプトが出てきて ユーザが許可する必要がありますが この確認プロンプトが出てきません
下のコードで requestPermission を実行しても何も起きず "prompt" が返ってきます
handle.requestPermission({ mode: "readwrite" })
いろいろ試してみると 拡張機能だからというわけではなく 実際に開いているページじゃないからのようです
バックグラウンドスクリプトやポップアップページでは確認プロンプトが出ませんでしたが ポップアップページの HTML をタブでページとして開くと確認プロンプトが出ました
バックグラウンドスクリプトでは ファイルを開くダイアログは出るものの確認プロンプトが出せず 読み込みのみという扱いにしかできませんでした
読み込みのみなら
読み込みだけでいいなら File System Access API 使わなくても拡張機能の manifest.json で{
"permissions": ["file:///*"]
}
のように file スキームを許可して file スキームにアクセスすればダイアログなしでアクセスできます
const xhr = new XMLHttpRequest()
xhr.open("GET", "file:///C:/Users/user1/Desktop/test.txt")
xhr.send()
xhr.onload = () => console.log(xhr.responseText)
fetch は file スキームをサポートしてないので XHR を使います
file スキーム内のパーミッションを求める拡張機能は怪しいので セキュリティ面では開くたびにユーザが選択したファイルしかアクセスできない File System Access API のほうが安心できそうです
ただ 基本自分用で file スキームも全許可求めて許可する使い方をしてると File System Access API の恩恵がなにもないんですよね
ページとして開いていなくても書き込みパーミッションの確認プロンプトが出るようになってほしいものです
追記
IndexedDB ならファイルハンドルを保存できるという情報をいただいたので 調べてみたらシリアライズ可能と書かれていましたpostMessage でも送れるんだとか
ということは拡張機能内の sendMessage でも送れる?と思って試してみました
しかし 結果は残念ながら送れませんでした
受け取った側では空のオブジェクトとして受け取っていました
拡張機能関係の対応は後回しみたいですね