先日、「PCブラウザでは問題なくPDF教材がダウンロードできるのに、スマホ(特にアプリ内ブラウザ)だとボタンを押しても1ミリも反応しない」という謎の不具合に遭遇しました。
原因を徹底的に調査したところ、「AIが気を利かせて書いた、PC向けの“賢い裏技コード”」が、「近年のスマートフォンおよびアプリ内ブラウザ(WebView)のガチガチなセキュリティ仕様」に真っ向から衝突していたことが分かりました。
今回は、なぜこの不具合が起きたのか、そしてどう解決したのかを技術的な背景とともにシェアします。
1. 何が起きていたのか?(不具合の現象)
対象のシステムでは、ボタンを押すと外部サーバー(Firebase Storage等)にあるPDFファイルをダウンロードできる機能がありました。
- PC環境(Chrome / Edge等): ボタンを押すと、即座にファイルが自動ダウンロードされる(期待通りの動き)。
- モバイル環境(iPhone Safari / 各種アプリ内ブラウザ): ボタンをタップしても、ダウンロードはおろか、画面遷移もプレビュー表示も何も起きない。
2. AI(Codex)が書いた「良かれと思った複雑なコード」
コードを解析したところ、ボタンが押された時の処理として、AIが以下のような非常にリッチなJavaScriptのロジックを実装していました。
// AIが提案した処理のイメージ(修正前)
async function downloadAttachment(url) {
// ① 非同期でデータを裏から取りに行く
const response = await fetch(url, { mode: 'cors' });
const blob = await response.blob(); // Blob化(データの一時ファイル化)
// ② 見えないリンク(aタグ)をプログラムで生成
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = "filename.pdf";
// ③ プログラムで強制的に疑似クリック
a.click();
// ④ 失敗した時の保険として、さらに時間差で別タブを開く
// window.open(url, '_blank');
}
なぜAIはこのコードを書いたのか?
外部のストレージサーバーにあるファイルをそのまま普通にリンク(<a href="...">)すると、「強制ダウンロードされずに、ブラウザ上でPDFプレビューが開いてしまう」という挙動になります。また、別サーバーのファイルに対して強制ダウンロード(download属性)を効かせようとすると、セキュリティ制限(CORS)に引っかかります。
AIはこれらを回避するために、「裏でデータをfetchして自分のサイトのデータ(Blob)に偽装し、見えないリンクを作って自動クリックさせる」という、PCにおける定番の「強制ダウンロードの裏技」を親切心から実装してくれたのです。
3. なぜスマホ(特にアプリ内ブラウザ)で全滅したのか?
この「PC向けの賢い裏技」が、スマホの「3つの厳格なセキュリティの壁」に阻まれました。
① 偽の自動クリック(a.click())の排除
スマートフォン(特にiOSのSafariなど)は、「人間が本当に指でタップしたリンク」しか信用しない傾向が強まっています。JavaScriptが裏側で勝手に作った見えないリンクの自動クリック(a.click())や、そこでの download 属性の挙動は、ユーザーの意図しない不正なファイル保存を防ぐために、スマホのOSレベルで無視(ブロック)されることがあります。
② 非同期処理のあとのポップアップブロック
裏技が失敗した時の保険として window.open() (別タブで開く)が用意されていましたが、これもスマホでは動きません。
スマホのブラウザは、「タップされた瞬間」の動きしか新しいタブの起動を許可しません。今回のコードのように、await fetch() という通信待ち(非同期処理)を挟んでから window.open() を実行すると、ブラウザは「ユーザーの操作ではなく、プログラムが勝手に画面を乗っ取ろうとした」と判断し、ポップアップブロックを強制発動します。
③ アプリ内ブラウザ(WebView)の「超・保守的」な制限
LINEや独自アプリなどの「アプリ内ブラウザ(WebView)」は、通常のSafariやChromeよりもさらにセキュリティが引き上げられています。 WebViewはアプリ開発者が挙動をコントロールできるため、悪意あるアプリがユーザーに気づかれないように裏でウイルスファイルをダウンロードさせる攻撃(ドライブバイダウンロード)が非常に警戒されています。そのため、プログラムによる自動ダウンロードや一時ファイル(Blob)へのアクセスは、一発で強制遮断される仕様になっています。
4. 解決策:あえて「王道(標準仕様)」に戻す
結論として、「JavaScriptで無理やり自動ダウンロードさせるリッチな処理」をすべて捨て、HTML標準のシンプルなリンクに戻すことにしました。
<a :href="fileUrl" target="_blank" rel="noopener noreferrer">
PDFを確認する
</a>
これで解決した理由
余計な通信(fetch)やBlob化、プログラムによる疑似クリックをすべて排除したため、ユーザーがタップした瞬間にブラウザの「正規のページ移動」として処理されます。そのため、スマホのポップアップブロックやセキュリティ制限に引っかかる要素がゼロになります。
タップすると別タブで綺麗にPDFプレビューが開き、ユーザーはスマホの標準機能(Safariの共有ボタンやChromeのメニューなど)を使って、いつでも安全に手動で端末へ保存できるようになりました。
まとめ:今回の教訓
- 「PCで動くかしこい裏技」は、スマホ(特にアプリ内ブラウザ)では「悪意ある挙動」とみなされてブロックされる。
- スマホのセキュリティ(WebView含む)は、画面が狭くて裏の動きが見えにくいユーザーを守るためにガチガチに作られている。
- 迷ったら、JSでの無理な制御をやめて、HTML標準の挙動(
<a>タグでの遷移)に委ねるのが最も堅牢でバグが起きにくい。
AIは非常に強力ですが、ターゲットが「スマホ環境」や「アプリ内WebView」である場合の細かい罠までは考慮しきれないことがあります。AIの提示したリッチなコードに惑わされず、モバイル環境では「シンプルイズベスト(ブラウザの標準機能に任せる)」という設計思想が大切だと痛感したエンジニアリングでした。

