ブラウザ拡張でダウンロードを振り分ける:ローカル保存・クラウド転送の設計と制限(Download Router)
はじめに
職場や個人のブラウザでファイルを扱うとき、「Downloads に置きたいもの」と「クラウドに直接送りたいもの」が混ざることは珍しくありません。Chrome / Edge など Chromium 系は、リンクを開く・保存する流れを内部で処理するため、拡張機能で挙動を差し替えると、「ローカル保存される/されない」「一時ファイルができる」といったブラウザ側の制限が設計の中心になります。
本記事は自作拡張 Download Router(manifest 1.2.0 時点)の設計メモとして、ローカル保存とクラウド転送がどう分岐するか、そして保証できないことを整理します。実装の中心は background.js、cloud-intercept.js、manifest.json です。
結論サマリ:ルール一致=常にローカル非保存ではない
| ルール種別 | ローカル(Downloads)への保存 |
|---|---|
| ローカルフォルダ宛て | suggest(folder/ファイル名) により 意図的にローカル保存 |
| クラウド(横取り成功) | 左クリックを preventDefault し、ダウンロード API を使わず fetch → アップロード。UUID .tmp を出さないのが主目的 |
| クラウド(横取り不可/オフ/別経路) | ブラウザのダウンロードが開始される。onDeterminingFilename で直送後に cancel/removeFile/erase するが、その間 一時 .tmp が付く可能性あり |
「ルール一致=常にローカルに残らない」は成立しません。 ローカル宛てルールでは残ります。クラウドでも 横取りできない条件ではダウンロードパイプラインが動きます。
クラウド転送の二経路(フロー)
-
経路 A(クリック横取り・推奨・既定オン)
ユーザーがhttp(s)リンクを左クリック →cloud-intercept.jsがキャプチャフェーズで処理 →cloudInterceptRulesと一致し、かつpreferCloudFetchWithoutDownloadが true ならpreventDefault→ メッセージで background のtryDirectCloudUploadへ。一致しない/設定オフなら 通常ナビまたはダウンロード。 -
経路 B(ダウンロード API)
downloads.onDeterminingFilenameでクラウドルール一致時にtryDirectCloudUpload→suggest後にsetTimeoutで cancel/removeFile/erase。
経路 A で横取りしなかったケースは、経路 B または通常ダウンロードに流れ込みます。
経路 A の補足
documentのclickを キャプチャで処理。- 条件:左クリックのみ(修飾キーなし)、
a[href]がhttp:/https:。 - chrome.storage.local の
cloudInterceptRulesスナップショットと照合。 settings.preferCloudFetchWithoutDownload(既定true)がfalseなら 横取りしない(background.jsのgetSettings既定、options.htmlでトグル)。
スナップショット(background.js の refreshInterceptableCloudRulesSnapshot):
- 有効かつ
canInterceptCloudRuleWithoutDownload(rule)なクラウドルールだけをcloudInterceptRulesに格納。 - 更新:
onInstalled/onStartup、アラームのトークン更新後、syncのrules変更、CLOUD_INTERCEPT_PAGE_READY(ページ可視化時はスロットル約 4 秒)。
横取り「可能」なルールの条件(スナップショットから外すもの)
canInterceptCloudRuleWithoutDownload は、次の条件タイプを 1 つでも含むルールをスナップショットから 除外します(クリック時点では URL/ファイル名しか確定しないため)。
mime_typefile_size_gt/file_size_ltuser_attribute(Entra)
AND/OR ロジックは cloud-intercept.js の evalRule が background と同型に再実装。除外タイプは content script で評価されないため、これらのみのルールは 経路 B に落ちます。
経路 B の補足
右クリック「名前を付けて保存」、別タブで直接開く、拡張が注入されないページ、download 属性なしの POST 系、横取り対象外ルールなどでダウンロードが開始された場合に該当します。
Edge/Chromium は保存先に UUID .tmp を先に作ることがあり、キャンセル前に 断片ファイルが一瞬〜しばらく存在し得ます。
横取り失敗時のフォールバック
cloud-intercept.js:sendMessage がエラー、または handled: false のとき window.location.assign(absUrl) で通常ナビに戻す。結果として 経路 B または通常ダウンロードになり得ます。
データの流れ:blob と .tmp(ローカル痕跡の制限)
| 場所 | 説明 |
|---|---|
| 経路 A | fetch 結果の Blob はサービスワーカー内。エクスプローラで辿るパスとしてのファイルではない(Office から直接開けない)。 |
| 経路 B | 並行してブラウザが Downloads 配下に .tmp へストリーム書込し得る。拡張の blob とは別経路。 |
| 期待の限界 | 「.tmp ゼロ・ディスク痕跡ゼロ」は 保証不能。経路 A で 確率は下がる。BitLocker 等の ディスク暗号化が別レイヤの防壁。 |
拡張機能単体では OS・Chromium の実装を越えた保証はできません。これがセキュリティ・コンプライアンス議論で押さえておきたい制限です。
ローカルフォルダルール
isCloudRule が偽のとき、suggest({ filename: folder/... }) のみ。キャンセルなし。Downloads 配下への ローカル保存が目的です。
介入しないケース(ブラウザ既定)
globalEnabled === false、ルールなし、forceEntraSignIn かつプロファイルなし、いずれのルールも不一致、onDeterminingFilename 内例外 → 素の suggest()。
付帯機能:通知
createCloudUploadNotification:成功時など openUrl があれば通知 ID と紐づけ、chrome.notifications.onClicked でタブを開く(cloud-providers.js が openUrl を返すプロバイダあり)。
実装上の注意:デッドコード
chrome.downloads.onChanged で pendingCloudUploads.get(id) を参照する **「完了後アップロード」**分岐は、現状 pendingCloudUploads.set が無いため到達しない。実効は 経路 A のメッセージおよび 経路 B の直送 + キャンセル。
設計上のトレードオフ(要約)
.tmpを出したくない → 経路 A に寄せる。ただし MIME/サイズ/Entra 条件付きルールや リンク以外のダウンロードは経路 B へ。- 認証付き URL(タブのクッキー必須)では、拡張の
fetchとブラウザのダウンロードで 取得可否が一致しないことがある(失敗時は履歴・通知で把握)。
現象とルールの突き合わせ(検証用)
| 観測 | 想定される要因 |
|---|---|
| Downloads に完成ファイルが残らない | 一致ルールが クラウドで、直送成功または失敗後も cancel/remove。または 経路 A でダウンロードを開始していない。 |
UUID .tmp が一瞬/残存 | 経路 B で Chromium が先に一時ファイルへ書き込んでいる。削除失敗時も残り得る。 |
| 「常にローカルに残らない」と感じるが残したい | 実際に一致しているのが クラウドルールの可能性。ローカルフォルダ用の別ルール・優先度を確認。 |
| ローカルフォルダ宛てなのに残らない | より優先度の高い クラウドルールが先に一致、globalEnabled / forceEntraSignIn など。履歴の ruleId を確認。 |
| 横取りしてほしいのに通常ダウンロード | preferCloudFetchWithoutDownload がオフ。または MIME / ファイルサイズ / Entra 条件。Cookie 必須 URL で handled: false など。 |
関連ファイル(拡張側)
| ファイル | 役割 |
|---|---|
background.js | ルール読込、直送、onDeterminingFilename、横取りメッセージ、履歴 |
cloud-intercept.js | クリック横取り、スナップショット照合 |
manifest.json | content_scripts で全 http(s) ページに cloud-intercept.js |
options.js / options.html | preferCloudFetchWithoutDownload トグル |
cloud-providers.js | cloudUpload の戻り値 openUrl 等 |
まとめ
ブラウザのダウンロードは多層の仕組みに依存するため、拡張機能だけで「常にローカル非保存」「一時ファイルゼロ」を約束するのは難しいです。Download Router では次のように整理しています。
- ローカルフォルダルール → 意図的なローカル保存(Downloads 配下)
- クラウドルール → クリック横取りで
.tmpリスクを下げ、フォールバック時はブラウザのダウンロード経路とその制限を受け入れる
同様の要件を検討している方の、設計レビューやセキュリティ要件のすり合わせの参考になれば幸いです。