数日前、とある方からメールを頂き、以下のような問題が現在進行系で発生しているのを確認しました。

Chrome Version 83.xにバージョンアップしたら、Google Apps Scriptのダイアログで表示してるリンクをクリックしてもファイルがダウンロードできない」という現象。

調べてみると既にいろいろな場所で報告があがっているのを確認しました。かなり広範囲に影響のある問題だと判明しています。

※Google SitesにGASアプリ埋め込んでリンクをクリックしても動かない等の現象もコレが原因。

※2020年7月30日、この問題はバグフィックスされて無事にこれまでの手法でダウンロードできるようになりました

今回の現象の詳細

現象の概要

下記のスクリーンショットのようにPDF化したものをクリックすると、Google DriveのPDFへの直リンクから本来ダウンロードできるはずが「右クリックして、新しいウィンドウで表示」をしてあげないと、ダウンロードがなされない。

また、スクリプトで新タブで表示するように仕込んでも表示されず、Enterキーで改めてURLを叩かないと実行されない現象が出ている。今回のファイルはこちらになります。

図:リンクをクリックしても反応無し

ソースコード

上記スクリーンショットはこのファイルのメニューから「PDF化新方式」⇒ダウンロードするを実行すると本来はPDFがダウンロードできなければならない。

GAS側コード

HTML側コード

  • https://drive.google.com/uc?export=download&id=といったファイル直リンクであっても動作しません。

問題のサンプル

以下のサンプルはリンクをクリックすると、PDF生成のリンクが実行されず、空白のタブが表示されるだけ。もう一度そのURLを手動で実行するとファイルがダウンロードされる・・・・本来はダイレクトにダウンロードされなければならない。

Issue Trackerによると・・・

Google Issue Trackerの情報によると、以下のように要約できます。

  • Chrome 81.xでは問題なく動作する
  • FirefoxやSafariでは問題なく動作する
  • Chrome 83.xからセキュリティ強化された結果、iframeのsandboxに「allow-downloads」が付いていない場合ダウンロードが実行されない
  • 同様にスクリプトにて、URLをHTML側でwindow.openで開いてもタブが開かれるだけでURLは実行されない(手動でそのURLをEnterキーで実行しなければならない)
  • 本来、.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)を付けることで、sandboxにallow-downloadsがつくべきなのに、ついていない為、動作しない
  • リンクを右クリック⇒名前を付けてリンク先を保存は機能する(唯一の回避策
  • GASのWebアプリはすべてiFrameのsandbox内で動作してるので、このようなトラブルに遭っている
  • trackerの返信に「I have reported the issue internally to add “allow-downloads” in HtmlService.XFrameOptionsMode.ALLOWALL so the downloads are allowed inside a GAS web app.」とあるので、次回以降の改修まで待たなければならない。
  • GASだけでなく、Teamsアプリ内でのファイルのダウンロードでも動作しなくなってる
  • jQueryを使ってリンクを自動でクリックするようにしても、結果は同じ。target属性で_blankであっても同様です。
  • Google Sitesに埋め込みで直接JavaScriptで記述しても、アレもiframeのsandboxなので同様の現象が発生します。
  • エラーとしては以下のようなコードがChrome Developer Toolsのコンソールに残ったりします。

Download is disallowed. The frame initiating or instantiating the download is sandboxed, but the flag ‘allow-downloads’ is not set. See

もしくは

Unsafe JavaScript attempt to initiate navigation for frame with origin ‘https://docs.google.com’ from frame with URL ‘https://n-aptkcbokyqzm7ogk27okuflxr3vmcyzizasmmxq-1lu-script.googleusercontent.com/userCodeAppPanel’. The frame attempting navigation of the top-level window is sandboxed, but the flag of ‘allow-top-navigation’ or ‘allow-top-navigation-by-user-activation’ is not set.

回避策

結論

現在、有効な回避策はありません。window.openを利用した自動ダウンロードは実行されませんし、ファイルへの直リンクを表示するようにしても、クリックしてダウンロードが実行されません。

唯一の代替案はリンクを右クリックで名前をつけて保存を手動で実行するのみ。Chrome81の方はアップデートを実行しないほうが良いでしょう(ただ、Chromeはサイレントアップデートで自動で最新になったりするので回避が難しい)

直接開くURLを例え短縮URLや転送サービスにしてもURLが開かれる事はなくブロックされてしまうほど強固です。ただしダウンロードを伴わないURLをポップアップで表示する分には問題なく表示されます。

トリッキーな回避策

ダウンロードを伴わないURLのオープンならば問題はないので、以下のような方法を実行してみた所うまく動作した。このトリッキーな方法を実現したサンプルスプレッドシートはこちら。余計なコード削ってないので、以下のexportPDFだけに注目してください。

  1. どこかHTMLを配置でき直接オープンできるサイトにindex.htmlファイルを配置する
  2. GAS側からsave pdfのダイアログで自動で開くサイトを1.のindex.htmlとする。この時?id=ファイルのIDとして生成したPDFファイルのIDをつなげたURLを開くようにする
  3. GASを実行すると2.のURLが自動でオープンされる。そしてそこにはdownloadリンクが表示されるので、クリックしてもらう(おそらくjQueryなどでクリックさせるようにしても動くんじゃないかな?)
  4. クリックしたら2.を閉じるスクリプトをindex.html側に入れておくのも良いかもしれない。

この時のソースは以下の通り

GAS側

  • 変数jsに配置したindex.htmlとパラメータとしてfileidを渡す文字列をつなげたURLを自動オープンさせるようにする
  • 開くとダイアログは自動で閉じて、新規タブで対象のURLがパラメータ付きで開かれる

配置するHTML側

  1. 呼び出された側のindex.htmlは起動時にgetParamを起動する
  2. getParamは現在のURLからid=以下のファイルのIDをこれで取得できる。
  3. element生成して、それに対して、download用のリンクを生成する。
  4. このdownloadリンクをユーザがクリックされるとGoogle Driveにダイレクト生成されたファイルが呼び出されてダウンロードが可能になる。
  5. jQueryでこのリンクを直接クリックさせるような動作を付け加えたり、クリック後に自身を閉じるようなスクリプトがあれば自動ダウンロードが可能になるのではないか?
  6. 今回はlink.clickで自動でリンクをクリックさせて直リンクを更に新しいタブで開いています。
  7. setTimeoutにて3秒後に自身のindex.htmlを閉じさせています。6.のタブはダウンロードが始まると自動で閉じてくれます。
  8. この方法はポップアップの手法なのでChrome側で予めポップアップを許可しておく必要があります。

なお、index.htmlはUTF-8 BOM付きでないと呼び出した時に文字化けするので注意。

回避策を適用したサンプル

見た目は問題のあるコードと同じですが、クリックすると外部に設置したHTMLが新しいウィンドウで開き、自動で生成したPDFファイルダウンロードのURLが実行され、ファイルがダウンロードされます。GASのsandbox外の出来事になるので、制約を受けずにこれまでの利便性を実現できます。

付記:HTML Serviceのオカシナ挙動

オカシナ結果が出るコード

お問い合わせにあった問題の解決策を探索中との事で頂いた中で、HTML Serviceのオカシナ挙動を見つけました。PDF生成リンクを作成しui.showDialogでHTMLタグを作り表示しクリックすると、生成したリンクの後半部分がオカシナ文字列に変換されるバグです。まだこの件はIssue Trackerにも報告はないようです。この問題のサンプルファイルはこちらになります。

上記のコードの時、ダイアログに表示されるリンクをクリックしても、「現在、ファイルを開くことができません。」と出てしまう。理由はURLの後半部分がオカシナ文字列に置き換えられてしまっている。以前は起きていなかった問題だと思われるのですが、URLを見てみると例えばscale=3が以下のようなオカシナ文字列に置き換わってしまっています。GAS側の問題なのか?それともChrome83の問題なのか。この問題はV8が有効であろうと無効であろうと発生しています。

図:URLがオカシナ文字列に置き換わっている

以下のようにHTML側でURLを組み立てた場合には正常に動作するので、createHtmlOutputではなくcreateHtmlOutputFromFileを使ってHTMLテンプレートを使った手法であれば問題なく表示が可能です。

修正したコード

GAS側コード

  • 通常通りのHTMLファイルを用意し、HTML側からgetparamを呼び出して値を返してあげているコードになります。
HTML側コード

  • 起動時にgoogle.script.run.withSuccessHandlerにてGAS側のgetparamを呼び出してパラメータを受け取る
  • 受け取ったパラメータでHTML側でURLを組み立てる
  • URLと生成したHTMLコードをID:clickmanに挿入する

ただしこの方法でも、現状はURLは正しく表示されるのですが、Chrome83の問題で新しいタブで開かれてもURLは実行されないので、Ctrlキー押しながらクリックするか?URLを手動でEnter実行しないとファイルはダウンロードされないのは同じです(Chrome83の問題が解決すればこのコードでPDFがダウンロードされるはずです)

関連リンク

共有してみる: