WebExtensions and clipboard operation

Chrome の拡張でクリップボードを読み書きするには、manifest.json の permissions に clipboardRead/clipboardWrite を追加した上で

  • 読み出す場合: バックグラウンドで適当な textarea に対して focus() し、document.execCommand(‘paste’) する。適当な textarea のその内容がクリップボードの内容で埋められる
  • 書き出す場合: バックグラウンドで適当な textarea に対して focus() し、内容を全選択した後 document.execCommand(‘copy’) する
  • つまり操作の実体はバックグラウンドになる。コンテントスクリプト側で読み書きの必要が発生したとしたら、バックグラウンドにメッセージを投げ、戻ってくるのを待つことになる。

    これが、WebExtension だとどうなるのかというと、ドキュメント自体は提供されているのだが、それによるとなんかどういう意図なのか知らないが、かなり Chrome の流儀と違う。

    まず short-lived event handler なる独自の概念が出てくる。これはつまり、ユーザーにより発生したイベント、のことらしい。しかしそれなら interactive event handler とか、あるいはそのまんま user generated event handler などと呼ぶべきものではないのか? 微妙によくわからない。何がどう short-lived なの? 意味がわからない。またもうひとつ独自の仕様があり、WebExtensions ではバックグラウンドスクリプト側でのクリップボードへの書き込みはできない。従って、コンテントスクリプト側で適当な textarea 要素に対して execCommand() しないといけない。つまり、Chrome と正反対なのである。

    で、この short-lived event handler 内であれば、clipboardWrite 権限なしにクリップボードへの書き込みは可能である。一方 short-lived ではないもの、つまりタイマによって起動された処理など、あるいはユーザーにより発生したイベント内で生成された Promise の一連の連鎖内も当てはまると思うが、そういう処理からはクリップボードへ書き込みを行うには clipboardWrite 権限を manifest.json に記述することが必要。

    ここまではドキュメントにそう書いてある通りで、その通りに wasavi を修正したならば、その通りに動作した。困るのはクリップボードからの読み出しなのであった。どうもまず、ドキュメントが言葉足らずで微妙に言いたいことがよくわからない。ドキュメントから読み取れるのは:

    • short-lived event handler 内で実行されるか否かに関係なく、常に clipboardRead 権限が必要
    • 読み込みも基本的にコンテントスクリプト側で処理?(サンプルが、適当な要素のクリックイベントを使用しているので)
    • 対象の要素は contentEditable モードでないといけない
    • コンテントスクリプト側で実行する場合、現状では textarea 要素のみに対応
    • バックグラウンド側ではいずれの要素も contentEditable モードにすることができる(できるから、何?)

    そもそもバックグラウンド側で実行できるのかどうかが明示されておらずよくわからない。クリップボードへの書き込みがバックグラウンド側では不可というのは、つまりバックグラウンドでは要素にフォーカスを当てるということができないからだそうだが、それなら読み込みも不可なんじゃないのか? しかし不可と明示されてはいないのである。わからん、ぜんぜんわからん。

    いろいろ試行錯誤してみたところ、以下のような手順でクリップボードの内容を取得することができた。

    • コンテントスクリプト側で実行する
    • ドキュメントに適当な textarea 要素を追加する。この要素のスタイルを [cci]display:none[/cci] とかにはできない。フォーカスを当てる必要があるので。その代わりにスクリーン外の適当な場所に追いやる必要がある
    • textarea 要素は contentEditable 属性を true にしておく必要がある。そもそも最初から編集可能なのに、なんでこの属性が必要なのか意味不明
    • textarea にフォーカスし、document.execCommand(‘paste’) を実行する
    • しかし、Chrome と違い、textarea の内容がクリップボードの内容で埋められたりは「しない」。なんと、なんと、その代わりに document に対して paste イベントが発生する。従って、execCommand 呼び出しの前に paste イベントハンドラを追加し、その中でクリップボードの内容を取り出しておく必要がある
    • なお paste イベントは、同期的に発生する

    どうも execCommand() により paste イベントが発生するという点がミソのようだ。その点を以ってバックグラウンドでも動作可能と匂わせているのかもしれない。ということは、コンテントスクリプト側で動かす際も textarea 要素に focus() する必要はないのか? そこまでは試してない。

    とりあえず、こんな感じのコードになる。

    function getClipboard () {
    let s = '';
    function handlePaste (e) {
    s = e.clipboardData.getData('text/plain');
    }
    let buffer = $('id_of_any_textarea');
    buffer.contentEditable = true;
    buffer.value = '';
    buffer.focus();
    document.addEventListener('paste', handlePaste, false);
    document.execCommand('paste');
    document.removeEventListener('paste', handlePaste, false);
    return s;
    }

    もちろん、すでに paste イベントを他の場所でハンドリングしている場合は、そのハンドラは一旦取り外し、事が済んだ後に再度追加する…的な小細工は必要。また、これはあくまで現状での動作なので、時が過ぎればころっと変えられる恐れは十二分にある。

    あの、なんで、こんなにも Chrome の流儀と全然違うデザインにしたんですか? WebExtensions 自体が Chrome の拡張に恐ろしく馴れ馴れしく擦り寄った代物であるのに、部分的に見るとまるで作法が異なるというのはどういう意図があるのかいまいち…というか、さっぱりわからない。技術的に何か制限があってこうなっているのならわからなくもないが、そうではないとしたら一言「アホじゃないの?」としか言いようがない。

One thought on “WebExtensions and clipboard operation

  1. Pingback: 赤心慶福 — WebExtensions and clipboard operation, revised

Leave a Reply

Your email address will not be published. Required fields are marked *