hogashi.*

日記から何から

文字列をコピーするブックマークレットで使うクリップボードのAPI改めて見てた

 ブックマークレットで文字列をコピーする (クリップボードに入れる) というのをよく書く。

 昔ググってへ〜と思ったまま Document.execCommand() - Web API | MDN をずっと使っていた。(あと HTMLInputElement.select() - Web APIs | MDN も知らなくて、 Selection を使って頑張って文字列選択していた。)

const value = 'これをコピーしたい';
// input をつくって body に入れる
const input = document.createElement('input');
document.querySelector('body').appendChild(input);
// フォーカスしたときスクロールされてしまわないように画面内に出す
input.style = 'position: fixed; top: 0; left: 0';
input.focus();
input.setSelectionRange(0, value.length);
document.execCommand('copy');
// input 消す
document.querySelector('body').removeChild(input);

 MDN を見ると廃止されている。

廃止: この機能は廃止されました。まだいくつかのブラウザーで動作するかもしれませんが、いつ削除されてもおかしくないので、使わないようにしましょう。

https://developer.mozilla.org/ja/docs/Web/API/Document/execCommand

 代わりに何を使ったらいいかここには書いていないけど、 クリップボード API - Web API | MDN というページがあって、 execCommand の代替であると書かれている。

クリップボード API は、クリップボードのコマンド (切り取り、コピー、貼り付け) に応答する機能や、システムクリップボードの非同期の読み取りや書き込みを行う機能を提供します。クリップボードの内容へのアクセスは、 Permissions API によって制限されています。ユーザーの許可がなければ、クリップボードの内容の読み取りや変更は許可されません。

この API は、 document.execCommand() を使用したクリップボードへのアクセスに取って代わるように設計されています。

https://developer.mozilla.org/ja/docs/Web/API/Clipboard_API

 Navigator.clipboard - Web API | MDNIE 以外のブラウザで存在する。しかし Clipboard - Web API | MDN を見ると、それぞれのメソッドのサポートは今のところまちまちになっている。クリップボードへの読み書きそれぞれについて、対象が Blob か文字列かでメソッドが分けられているのが面白い。そのおかげもあって、よく見ると、 Clipboard.writeText() - Web APIs | MDN だけはどのブラウザでもすでに (ユーザに許可を求めたりせずに) すっと使えそう。

const value = 'これをコピーしたい';
navigator.clipboard.writeText(value);

 コードの量がめっちゃ短くなって、使い方も (文字列選択してから execCommand するより) 直感的で便利。めでたし、と思いきや、実はむずい点がひとつあって、 document にフォーカスがある必要がある。
 Clipboard API and events を見ると、破滅しないためにそうしているっぽい。確かにそのページを開いたまま別の作業をしてる間にクリップボードの中身がバリバリ変わったら困る。

The Asynchronous Clipboard API is a powerful feature because it can allow access to the clipboard data from any script (access is not restricted to Clipboard Event handlers) and data can be accessed in the absence of a user provided gesture.

To help prevent abuse, this API must not be available unless the script is executing in the context of a document that has focus.

https://www.w3.org/TR/clipboard-apis/#privacy-async

 ということは、ブックマークレットでは困る場合がある。開発者ツールとかアドレスバーとかみたいな document 以外の場所にフォーカスがあるとコピーできない。開発者ツールの console にはこういうエラーが出る。

Uncaught (in promise) DOMException: Document is not focused.

 JavaScript で document にフォーカスすることはできなさそう (それはそうで、それができてしまうとこの制限の意味がない)。画面内をクリックしてからブックマークレットを実行しないといけないのか、不便で困った、と思いきや、ブックマークレットを連続で 2回実行すると成功することに気付く。これは Chrome でも Firefox でもそういう感じだった。
 面白いことに、ブックマークをクリックすると document にフォーカスされるっぽい……。なので、 2回ブックマークレットをクリックすることを念頭に使えば、どういう場合でもコピーに成功する。 document.hasFocus() - Web API | MDN で document にフォーカスがあるか調べられる。

f:id:hogashi:20210930014938p:plain
最初はアドレスバーにフォーカスを置いていた / ブックマークレットを実行するとエラーが出て、 document にフォーカスされた

 さらによく考えると、ユーザがブックマークをクリックして document がフォーカスされた後にコピーしたらいいので、 WindowOrWorkerGlobalScope.setTimeout() - Web API | MDN でちょっとだけ待ってあげると、 document にフォーカスがある状態でコピーできて成功する。素朴。

const value = 'これをコピーしたい';
setTimeout(() => navigator.clipboard.writeText(value), 100);

 文書内のボタンとかでコピーするようなときは何も気にせず使えて便利だけど、ブックマークレットだとちょっと難しい、しかしブラウザの挙動の感じで偶然セーフ、という感じで面白かった。