← ブログ一覧に戻る
技術解説

ブラウザ拡張でダウンロードを振り分ける:ローカル保存・クラウド転送の設計と制限(Download Router)

#ブラウザ#拡張機能#ローカル保存#制限#ダウンロード#クラウド

はじめに

職場や個人のブラウザでファイルを扱うとき、「Downloads に置きたいもの」と「クラウドに直接送りたいもの」が混ざることは珍しくありません。Chrome / Edge など Chromium 系は、リンクを開く・保存する流れを内部で処理するため、拡張機能で挙動を差し替えると、「ローカル保存される/されない」「一時ファイルができる」といったブラウザ側の制限が設計の中心になります。

本記事は自作拡張 Download Router(manifest 1.2.0 時点)の設計メモとして、ローカル保存とクラウド転送がどう分岐するか、そして保証できないことを整理します。実装の中心は background.jscloud-intercept.jsmanifest.json です。


結論サマリ:ルール一致=常にローカル非保存ではない

ルール種別ローカル(Downloads)への保存
ローカルフォルダ宛てsuggest(folder/ファイル名) により 意図的にローカル保存
クラウド(横取り成功)左クリックを preventDefault し、ダウンロード API を使わず fetch → アップロード。UUID .tmp を出さないのが主目的
クラウド(横取り不可/オフ/別経路)ブラウザのダウンロードが開始される。onDeterminingFilename で直送後に cancel/removeFile/erase するが、その間 一時 .tmp が付く可能性あり

「ルール一致=常にローカルに残らない」は成立しません。 ローカル宛てルールでは残ります。クラウドでも 横取りできない条件ではダウンロードパイプラインが動きます。


クラウド転送の二経路(フロー)

  1. 経路 A(クリック横取り・推奨・既定オン)
    ユーザーが http(s) リンクを左クリック → cloud-intercept.js がキャプチャフェーズで処理 → cloudInterceptRules と一致し、かつ preferCloudFetchWithoutDownload が true なら preventDefault → メッセージで background の tryDirectCloudUpload へ。一致しない/設定オフなら 通常ナビまたはダウンロード

  2. 経路 B(ダウンロード API)
    downloads.onDeterminingFilename でクラウドルール一致時に tryDirectCloudUploadsuggest 後に setTimeout で cancel/removeFile/erase。

経路 A で横取りしなかったケースは、経路 B または通常ダウンロードに流れ込みます。


経路 A の補足

  • documentclickキャプチャで処理。
  • 条件:左クリックのみ(修飾キーなし)、a[href]http: / https:
  • chrome.storage.localcloudInterceptRules スナップショットと照合。
  • settings.preferCloudFetchWithoutDownload(既定 true)が false なら 横取りしないbackground.jsgetSettings 既定、options.html でトグル)。

スナップショットbackground.jsrefreshInterceptableCloudRulesSnapshot):

  • 有効かつ canInterceptCloudRuleWithoutDownload(rule) なクラウドルールだけを cloudInterceptRules に格納。
  • 更新:onInstalled / onStartup、アラームのトークン更新後、syncrules 変更、CLOUD_INTERCEPT_PAGE_READY(ページ可視化時はスロットル約 4 秒)。

横取り「可能」なルールの条件(スナップショットから外すもの)

canInterceptCloudRuleWithoutDownload は、次の条件タイプを 1 つでも含むルールをスナップショットから 除外します(クリック時点では URL/ファイル名しか確定しないため)。

  • mime_type
  • file_size_gt / file_size_lt
  • user_attribute(Entra)

AND/OR ロジックは cloud-intercept.jsevalRule が background と同型に再実装。除外タイプは content script で評価されないため、これらのみのルールは 経路 B に落ちます。

経路 B の補足

右クリック「名前を付けて保存」、別タブで直接開く、拡張が注入されないページ、download 属性なしの POST 系、横取り対象外ルールなどでダウンロードが開始された場合に該当します。

Edge/Chromium は保存先に UUID .tmp を先に作ることがあり、キャンセル前に 断片ファイルが一瞬〜しばらく存在し得ます。

横取り失敗時のフォールバック

cloud-intercept.jssendMessage がエラー、または handled: false のとき window.location.assign(absUrl) で通常ナビに戻す。結果として 経路 B または通常ダウンロードになり得ます。


データの流れ:blob.tmp(ローカル痕跡の制限)

場所説明
経路 Afetch 結果の 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.jsopenUrl を返すプロバイダあり)。


実装上の注意:デッドコード

chrome.downloads.onChangedpendingCloudUploads.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.jsoncontent_scripts で全 http(s) ページに cloud-intercept.js
options.js / options.htmlpreferCloudFetchWithoutDownload トグル
cloud-providers.jscloudUpload の戻り値 openUrl

まとめ

ブラウザのダウンロードは多層の仕組みに依存するため、拡張機能だけで「常にローカル非保存」「一時ファイルゼロ」を約束するのは難しいです。Download Router では次のように整理しています。

  • ローカルフォルダルール → 意図的なローカル保存(Downloads 配下)
  • クラウドルール → クリック横取りで .tmp リスクを下げ、フォールバック時はブラウザのダウンロード経路とその制限を受け入れる

同様の要件を検討している方の、設計レビューやセキュリティ要件のすり合わせの参考になれば幸いです。