mizusame

【Blogger】プレビューで Clipboard API を使えるようにする

当ブログでは、読者の方が簡単にコードをコピーできるよう、コードブロックにコピーボタンを付けています。

上記リンクのタイトルの通り Clipboard API を使っているんですが、少なくとも Google Chrome では Blogger のプレビュー上で動いてくれません。

記事を書いている最中、コードブロックに載せたコードがちゃんと動くのか確認したいとき、コピーボタンが使えなくて困ります。

そこ今回は、Blogger のプレビューで Clipboard API が使えない原因と、それを解決する方法について書いていきます。

アイキャッチ

プレビューで Clipboard API が使えない原因

例えば Blogger の記事内に、以下のクリックイベントを登録したボタンを設置するとします。

async function copyText(){
  try{
    await navigator.clipboard.writeText('コピーしたいテキスト');
    alert('コピーしました!!');
  } catch(e){
    alert('コピーできませんでした...');
    console.log(e.message);
  }
}

上記のコードは、実際の記事上では Clipboard API 非対応のブラウザでなければ正常に動きます。

しかし、Chrome にてプレビュー上でボタンをクリックしても、コピー失敗のアラートとともにデベロッパーツールのコンソールログに以下のエラーメッセージが出力されます。

Not Allowed Error: Failed to execute 'writeText' on 'Clipboard': The Clipboard API has been blocked because of a permissions policy applied to the current document. See https://goo.gl/EuHzyv for more details.

(和訳:許可されていないエラー: 「Clipboard」で「writeText」を実行できませんでした: 現在のドキュメントに適用されている権限ポリシーにより、Clipboard API がブロックされました。詳細については、https://goo.gl/EuHzyv を参照してください。)

このエラーの原因は、Permissions Policy(権限ポリシー)による制限です。Permissions Policy とは、Web ページや iframe が使用できる特定の機能(位置情報やカメラ、マイクなど)へのアクセス権限を、Web 開発者が細かく制御できる仕組みのことです。

以下のページには、この Permissions Policy によって、クロスオリジン iframe の一部機能へのアクセス権限がデフォルトでブロックされる、と書いてあります。

クロスオリジン とは、異なるオリジン間でのリソースのやり取りを指します。URL のプロトコル、ホスト名、ポート番号を合わせたものをオリジンと呼び、例えば オリジンが https://example.com:443 の場合、https: がプロトコル、www.example.com がホスト名、443 がポート番号となります。

Blogger のプレビューにおいて、プレビューを表示している iframe のオリジンは https://*.blogspot.com、その親ページのオリジンは https://www.blogger.com です。このため、プレビューの iframe はクロスオリジンで動作していると言えます。

プレビューを表示しているクロスドメイン iframe の Clipboard API への権限が、Permissions Policy によって拒否されてしまったため、コピーボタンが動かなくなったのです。

前述した記事「Deprecating Permissions in Cross-Origin Iframes」には、クロスドメイン iframeallow 属性を用いれば、機能への権限を Permissions Policy によって許可できると書かれています。

しかし、Blogger のプレビューを表示している iframe には、以下のコードからもわかるように allow 属性が存在しません。

<iframe class="*" jsname="*" name="previewFrame" src="//*.blogspot.com/b/blog-preview?token=*" aria-label="*のプレビュー表示"></iframe>

今まで述べてきたことをまとめると、つまりは以下の2点が、Blogger のプレビューで Clipboard API が使えなかった原因と言えます。

  • クロスドメイン iframe 内での Clipboard API へのアクセス権限が、Permissions Policy によりデフォルトで拒否されていること
  • プレビューを表示している iframeallow 属性が存在しないため、Clipboard API へのアクセスが依然として拒否されたままであること

プレビューで Clipboard API を使えるようにする

前項では Blogger のプレビューで Clipboard API を使えない原因について述べました。今度はその原因を突破する方法について書いていこうと思います。

iframe に allow 属性を追加する

以下のページの「セキュリティの考慮」の欄に、Chromium 系のブラウザにおいて Clipboard API の権限を許可する方法が書かれていました(Firefox と Safari は未対応)。

こちらのページでは HTTP の Permissions-Policy にしか言及されていませんが、iframeallow 属性でも同じような方法で設定できます。

プレビューを表示している iframe に、以下のように allow="clipboard-write; clipboard-read" を追加すれば、Clipboard API を使えるようになります。

<iframe allow="clipboard-write; clipboard-read" class="*" jsname="*" name="previewFrame" src="//*.blogspot.com/b/blog-preview?token=*" aria-label="*のプレビュー表示"></iframe>

書き込みの権限を許可するには clipboard-write、読み取りの権限を許可するには clipboard-read が必要です。どちらかの権限しか使わない場合は、属性値の指定もどちらかだけで構いません。

もう少し細かく allow 属性を設定したいときは、以下のページが参考になります。

で、実際にどうプレビューの iframeallow 属性を追加するかですが、ブックマークレットを使うことにしました。

以下のコードをブックマークの URL 欄にコピーし、名前を自分のわかりやすいものにして登録します。

javascript:(()=>{const%20preview=document.querySelector('iframe[name="previewFrame"]');if(!preview){alert('このページはプレビューではありません...');return;}preview.allow='clipboard-write;clipboard-read';alert('Clipboard%20API%20が使えるようになりました!!');preview.src=preview.src;})();
圧縮前のコード
javascript:
(() => {
  const preview = document.querySelector('iframe[name="previewFrame"]');
  if(!preview){
    alert('このページはプレビューではありません...');
    return;
  }
  preview.allow = 'clipboard-write; clipboard-read';
  alert('Clipboard API が使えるようになりました!!');
  preview.src = preview.src;
})();

プレビューでブックマークレットをクリックすると、プレビューが表示される iframe に、allow="clipboard-write; clipboard-read" を追加し、アラートを表示します。アラートを消すと、iframe が再読み込みされます。

これで、プレビューの中でも Clipboard API が動くようになります。

ここからさらにプレビューを更新したいときは、プレビュー下部に表示される「このプレビューを再読み込みする」ボタンを使います。

ブラウザの更新ボタンや F5 キー、投稿エディタの「投稿をプレビュー」ボタンを使うと親ページごとリロードされ、せっかく設定した iframeallow 属性が消えてしまうのでご注意ください。

iframe を使わずに直接プレビューを表示する

そもそも親ページと違うオリジンのプレビューを iframe で表示してるせいで Clipboard API が動かないわけだから、iframe を使わずにプレビューを直接タブで開けばいいのではと思いました。

前項と同じくブックマークレットを使います。

以下のコードをブックマークの URL 欄にコピーし、名前を自分のわかりやすいものにして登録します。

javascript:(()=>{const%20preview=document.querySelector('iframe[name="previewFrame"]');if(!preview){alert('このページはプレビューではありません...');return;}location.href=preview.src;})();
圧縮前のコード
javascript:
(() => {
  const preview = document.querySelector('iframe[name="previewFrame"]');
  if(!preview){
    alert('このページはプレビューではありません...');
    return;
  }
  location.href = preview.src;
})();

プレビューでブックマークレットをクリックすると、プレビューが表示される iframesrc 属性に指定されている URL に遷移します。

この方法でも、プレビューの中の Clipboard API が動くようになります。

あとがき

この記事では、Blogger のプレビューで Clipboard API が使えない原因と、それを解決する方法について述べました。プレビューは iframe で表示されるため Clipboard API が使えない、くらいの簡単な話かと思っていたら、Permissions Policy だのクロスオリジンだの聞きなじみのなかったワードが関係してきたのでだいぶ面食らいました。

今まで書いた中で一番苦労した記事かもしれません。特にクロスオリジンは、異なるオリジン間のリソースのやり取りことなのか、異なるオリジン自体のことなのかわからず、結構曖昧な書き方をしています。クロスオリジンリソース共有(CORS)の解説記事は見つかるんですが、クロスオリジンそれ自体をちゃんと説明してる記事がなかなか見つからなくて……。間違ったこと書いてたらすみません。

そんな感じで自分としては釈然としない部分もあるものの、プレビューで Clipboard API を使えるようにはできたのでよかったです。終わりよければ全てよし!

編集
ホーム