ファイルシステムアクセス

ドラフトコミュニティグループレポート,

このバージョン:
https://wicg.github.io/file-system-access/
課題の追跡:
GitHub
仕様内インライン
編集者:
(Google)
以前の編集者:
(Google)
(Google)

要約

このドキュメントは、[FS]のAPIを拡張し、開発者がユーザーのローカルデバイス上のファイルとやり取りできる強力なWebアプリを構築できるようにします。 ファイル読み取り機能のためにFile APIをベースにし、ファイルの編集およびディレクトリ操作を可能にする新しいAPIの機能を追加します。

このドキュメントのステータス

この仕様書は、Web Platform Incubator Community Groupによって公開されました。 これはW3C標準でもなく、W3Cの標準策定プロセスにも含まれていません。 W3C コミュニティ貢献者ライセンス契約(CLA)の下で限定的なオプトアウトがあり、他の条件も適用されますのでご注意ください。 W3C コミュニティおよびビジネスグループについて詳しくはご覧ください。

1. はじめに

このセクションは規範的ではありません。

このAPIにより、開発者はユーザーのデバイス上のファイルシステムを介して他の(Web以外の)アプリと連携する強力なアプリを構築できます。ユーザーがこの機能を期待する主なアプリケーション例としては、IDE、写真や動画編集ソフト、テキストエディタなどがあります。 ユーザーがウェブアプリへのアクセスを許可した後、このAPIを利用することで、そのアプリはユーザーのデバイス上のファイルやフォルダーに直接読み書きできるようになります。ファイルの読み書きに加えて、このAPIはディレクトリを開いてその内容を列挙することも可能にします。さらに、ウェブアプリは利用可能になったファイルやディレクトリへの参照を保存でき、ユーザーが再度同じファイルを選択することなく、同じコンテンツへのアクセスを再取得できます。

このAPIは、<input type=file>や <input type=file webkitdirectory> [entries-api] と同様に、ユーザー操作はファイルやディレクトリのピッカーダイアログを通じて行われます。 ただし、これらのAPIとは異なり、このAPIは現在純粋にJavaScript APIであり、フォームおよびinput要素とは統合されていません。

このAPIは、[FS]に記載されたAPIを拡張しています。そこでは、ウェブサイトがユーザーへ明示的なアクセス許可を求めなくても取得できるバケットファイルシステムが定義されています。

2. ファイルとディレクトリ

2.1. 概念

有効なサフィックスコードポイントとは、コードポイントのうち、ASCII英数字、 U+002B(+)、またはU+002E(.)であるものを指します。

注: これらのコードポイントは、ほとんどの既存ファイル形式をサポートするために選ばれています。 ファイル拡張子の大半は英数字のみですが、.tar.gzのような複合拡張子や、C++ソースコードのための.c++のような拡張子もよく使われるため、「+」と「.」も許容されています。

2.2. パーミッション

"file-system" 強力な機能のパーミッションに関するアルゴリズムと型は以下の通りです:

パーミッションディスクリプタ型

FileSystemPermissionDescriptor は以下のように定義されます:

enum FileSystemPermissionMode {
  "read",
  "readwrite"
};

dictionary FileSystemPermissionDescriptor : PermissionDescriptor {
  required FileSystemHandle handle;
  FileSystemPermissionMode mode = "read";
};
パーミッション状態の制約
FileSystemPermissionDescriptor descに対するパーミッション状態の制約を決定するには、以下の手順を実行します:
  1. entryをdesc["handle"]の entryとします。

  2. もしentryがファイルシステムエントリであり、 バケットファイルシステム内にある場合、 このディスクリプタのパーミッション状態は常に "granted"になります。

  3. それ以外で、entryの親がnullでない場合、 このディスクリプタのパーミッション状態は、 同じmodeで entryの親を表すhandleを持つ ディスクリプタのパーミッション状態と等しくしなければなりません。

  4. そうでなく、もしdesc["mode"] が "readwrite"の場合:

    1. read stateを、同じhandle で mode が "read" となるディスクリプタのパーミッション状態とします。

    2. もしread stateが"granted"でなければ、 このディスクリプタのパーミッション状態 はread stateと等しくなります。

これらのチェックはentryに紐付けないようにするべきです。[whatwg/fs Issue #101]

パーミッションリクエストアルゴリズム
FileSystemPermissionDescriptor desc と PermissionStatus status が与えられた場合、以下の手順を実行します:
  1. desc と status で デフォルト権限クエリアルゴリズム を実行します。

  2. status の state が "prompt" でない場合、これらのステップを中止します。

  3. settings を desc["handle"] の 関連設定オブジェクト とします。

  4. global を settings の グローバルオブジェクト とします。

  5. global が Window でない場合、 "SecurityError" DOMException を スロー します。

  6. global が 一時的アクティベーション を持たない場合、 "SecurityError" DOMException を スロー します。

  7. settings の オリジン が settings の トップレベルオリジンと 同一オリジン でない場合、 "SecurityError" DOMException を スロー します。

  8. desc を使用する権限を リクエスト します。

  9. desc と status で デフォルト権限クエリアルゴリズム を実行します。

このユーザーアクティベーションの要件は上流で定義されるのが理想的です。 [WICG/permissions-request Issue #2]

ファイルシステム権限の照会 をFileSystemHandle handle および FileSystemPermissionMode mode とともに実行するには、以下の手順を行います:
  1. descをFileSystemPermissionDescriptorとします。

  2. desc["name"]に "file-system"を設定します。

  3. desc["handle"] にhandleを設定します。

  4. desc["mode"] にmodeを設定します。

  5. descのパーミッション状態を返します。

ファイルシステム権限のリクエスト をFileSystemHandle handle および FileSystemPermissionMode mode とともに実行するには、以下の手順を行います:
  1. descをFileSystemPermissionDescriptorとします。

  2. desc["name"] に "file-system"を設定します。

  3. desc["handle"] にhandleを設定します。

  4. desc["mode"] にmodeを設定します。

  5. descのPermissionStatusを作成を実行し、その結果をstatusとします。

  6. "file-system"機能に対して、 パーミッションリクエストアルゴリズムを descとstatusで実行します。

  7. descのパーミッション状態を返します。

現時点では FileSystemPermissionMode には"read" または"readwrite"のみがあります。 将来的にはwrite専用ハンドルをサポートするため"write"モードも追加するかもしれません。 [Issue #119]

2.3. FileSystemHandle インターフェース

dictionary FileSystemHandlePermissionDescriptor {
  FileSystemPermissionMode mode = "read";
};

[Exposed=(Window,Worker), SecureContext, Serializable]
partial interface FileSystemHandle {
  Promise<PermissionState> queryPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
  Promise<PermissionState> requestPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
};

2.3.1. queryPermission() メソッド

status = await handle . queryPermission({ mode : "read" })
status = await handle . queryPermission()
status = (await navigator.permissions.query({ name : "file-system", handle : handle })).state

このハンドルの読み取り権限の現在の状態を照会します。 もしこれが"prompt" を返す場合、ウェブサイトは requestPermission() を操作前に呼び出す必要があります。 これが"denied" を返す場合、すべての操作は拒否されます。

ローカルファイルシステムハンドルファクトリーから返されるハンドルは、初期状態では通常 "granted" を返しますが、ユーザーが権限を取り消した場合や、IndexedDBから取得されたハンドルについては "prompt" を返す場合があります。

status = await handle . queryPermission({ mode : "readwrite" })
status = (await navigator.permissions.query({ name : "file-system", handle : handle, mode : "readwrite" }).state

このハンドルの書き込み権限の現在の状態を照会します。 これが"prompt" を返す場合、このハンドルが表すファイルやディレクトリへ変更しようとする際、ユーザー操作が必要となり、ユーザーに確認プロンプトが表示されます。 ただし、もしこのハンドルの読み取り権限の状態も"prompt" の場合、ウェブサイトは requestPermission() を呼び出す必要があります。 ファイルやディレクトリの読み取りの際、読み取りアクセスに自動的な許可プロンプトは表示されません。

permissions APIのquery() メソッドとの統合はまだChromeには実装されていません。

queryPermission(descriptor) メソッドを呼び出すと、以下の手順を実行します:
  1. resultを新しいpromiseとする。

  2. 以下の手順を並列で実行する:

    1. stateを、 ファイルシステム権限の照会の this と descriptor["mode"] を与えて実行した結果とする。

    2. resolve result with state。

  3. resultを返す。

2.3.2. requestPermission() メソッド

status = await handle . requestPermission({ mode : "read" })
status = await handle . requestPermission()

このハンドルの読み取り権限の状態が"prompt" 以外の場合、その状態をそのまま返します。 もし"prompt" の場合はユーザーのアクティベーションが必要となり、ユーザーに確認プロンプトが表示されます。 その後ユーザーの応答により新しい読み取り権限の状態が返ります。

status = await handle . requestPermission({ mode : "readwrite" })

このハンドルの書き込み権限の状態が"prompt" 以外の場合、その状態をそのまま返します。 もしこのハンドルの読み取り権限の状態が"denied" なら、それを返します。

それ以外の場合で書き込み権限が"prompt" ならユーザーに確認プロンプトが表示されます。 そしてユーザーの選択によって新しい書き込み権限の状態が返ります。

requestPermission(descriptor) メソッドを呼び出すと、以下の手順を実行します:
  1. resultを新しいpromiseとする。

  2. 以下の手順を並列で実行する:

    1. stateを ファイルシステム権限のリクエストの thisと descriptor["mode"] を与えて実行した結果とする。 それが例外を投げた場合はresultをその例外で拒否し中止する。

    2. resolve result with state。

  3. resultを返す。

3. ローカルファイルシステムへのアクセス

enum WellKnownDirectory {
  "desktop",
  "documents",
  "downloads",
  "music",
  "pictures",
  "videos",
};

typedef (WellKnownDirectory or FileSystemHandle) StartInDirectory;

dictionary FilePickerAcceptType {
    USVString description = "";
    record<USVString, (USVString or sequence<USVString>)> accept;
};

dictionary FilePickerOptions {
    sequence<FilePickerAcceptType> types;
    boolean excludeAcceptAllOption = false;
    DOMString id;
    StartInDirectory startIn;
};

dictionary OpenFilePickerOptions : FilePickerOptions {
    boolean multiple = false;
};

dictionary SaveFilePickerOptions : FilePickerOptions {
    USVString? suggestedName;
};

dictionary DirectoryPickerOptions {
    DOMString id;
    StartInDirectory startIn;
    FileSystemPermissionMode mode = "read";
};

[SecureContext]
partial interface Window {
    Promise<sequence<FileSystemFileHandle>> showOpenFilePicker(optional OpenFilePickerOptions options = {});
    Promise<FileSystemFileHandle> showSaveFilePicker(optional SaveFilePickerOptions options = {});
    Promise<FileSystemDirectoryHandle> showDirectoryPicker(optional DirectoryPickerOptions options = {});
};

showOpenFilePicker()、 showSaveFilePicker() および showDirectoryPicker() メソッドは ローカルファイルシステムハンドルファクトリー として知られています。

注: 本仕様で"ローカルファイルシステム"と呼ばれるものは、厳密にローカルデバイス上のファイルシステムである必要はありません。ここでのローカルファイルシステムは、クラウドプロバイダーをバックエンドにしてもよいです。例えばChrome OSでは、これらのファイルピッカーでGoogleドライブ上のファイルやディレクトリも選択できます。

Chrome バージョン85以前では、これは汎用的なchooseFileSystemEntriesメソッドとして実装されていました。

3.1. ローカルファイルシステムのパーミッション

ユーザーがローカルファイルシステムハンドルファクトリーによるプロンプトで指定ファイルを選択したという事実は、ユーザーがそのファイルへの読み取り権限をウェブサイトに付与する意図であるとユーザーエージェントが扱うべきです。そのため ローカルファイルシステムハンドルファクトリー のいずれかによって返されたPromiseが解決された時点で、handle を返されたハンドルに設定し、 mode を"read" に設定したディスクリプタのパーミッション状態は"granted" であるべきです。

さらにshowSaveFilePicker の呼び出しでは、返されたハンドルをhandle に、 mode を"readwrite" に設定したディスクリプタのパーミッション状態は"granted" であるべきです。

environmentがファイルピッカーを表示してよいかを検証するには、次を実行します:
  1. environment の origin が opaque origin である場合、 "SecurityError" DOMException で 拒否された約束 を返します。

  2. environment の origin が environment の トップレベル origin と 同一オリジン でない場合、 "SecurityError" DOMException で 拒否された約束 を返します。

  3. global を environment の グローバルオブジェクト とします。

  4. global が 一時的アクティベーション を持たない場合、 "SecurityError" DOMException を スロー します。

3.2. ファイルピッカーのオプション

3.2.1. 受け入れられるファイルタイプ

showOpenFilePicker(options) および showSaveFilePicker(options) メソッドは、 FilePickerOptions 引数を受け入れ、これによりウェブサイトはファイルピッカーがユーザーに選択させるファイルのタイプを指定できます。

types の各エントリは、ファイルピッカーに表示されるファイルをフィルタリングするための、ユーザーが選択可能な単一のオプションを指定します。

各オプションは、オプションの description と、MIME タイプと拡張子の数(MIME タイプから拡張子のリストへのマッピングとして指定)で構成されます。説明が提供されない場合、説明が生成されます。 拡張子は "." で始まり、有効なサフィックスコードポイントのみを含む文字列でなければなりません。 さらに拡張子は16コードポイントの長さに制限されます。

完全な MIME タイプに加えて、MIME タイプのサブタイプとして "*" を使用して、たとえば "image/*" ですべての画像形式に一致させることができます。

ウェブサイトは常に各オプションに対して MIME タイプとファイル拡張子の両方を指定すべきです。ファイル拡張子のみを使用してファイルタイプを記述するプラットフォームでは ユーザーエージェントは拡張子で一致させることができ、拡張子を使用しないプラットフォームでは ユーザーエージェントは MIME タイプで一致させることができます。

デフォルトではファイルピッカーは、フィルタを適用しないオプションも含み、 ユーザーが任意のファイルを選択できるようにします。 excludeAcceptAllOption を true に設定して、 ファイルピッカーにこのオプションを含めないようにします。

たとえば、次のオプションではユーザーは3つの異なるフィルタのいずれかを選択できます。 テキストファイル(プレーンテキストまたはHTML)のもの、画像のもの、そしてフィルタを適用せずユーザーが任意のファイルを選択できる3番目のもの。

const options = {
  types: [
    {
      description: 'Text Files',
      accept: {
        'text/plain': ['.txt', '.text'],
        'text/html': ['.html', '.htm']
      }
    },
    {
      description: 'Images',
      accept: {
        'image/*': ['.png', '.gif', '.jpeg', '.jpg']
      }
    },
  ],
};

一方、次の例ではユーザーはSVGファイルのみを選択できます。ダイアログは フィルタを適用しないオプションを表示しません。

const options = {
  types: [
    {
      accept: {
        'image/svg+xml': '.svg'
      }
    },
  ],
  excludeAcceptAllOption: true
};
accept types を処理するには、FilePickerOptions options を与えて、次の手順を実行します:
  1. accepts options を、説明とフィルタからなるリストの空のタプルとします。

  2. options["types"] の各 type について繰り返します:

    1. type["accept"] の各 typeString → suffixes について繰り返します:

      1. parsedType を、parse a MIME type を typeString で実行した結果とします。

      2. parsedType が failure の場合、throw a TypeError.

      3. parsedType の parameters が空でない場合、 throw a TypeError.

      4. suffixes が文字列の場合:

        1. suffixes を与えてvalidate a suffix を実行します。

      5. それ以外の場合、suffixes の各 suffix について繰り返します:

        1. suffix を与えてvalidate a suffix を実行します。

    2. filter を、これらの手順(filename(string)と type(MIME type)を与えて)とします:

      1. type["accept"] の各 typeString → suffixes について繰り返します:

      2. parsedType を、parse a MIME type を typeString で実行した結果とします。

        1. parsedType の subtype が "*" の場合:

          1. parsedType の type が "*" の場合、true を返します。

          2. parsedType の type が type の type の場合、true を返します。

        2. parsedType の essence が type の essence の場合、 true を返します。

        3. suffixes が文字列の場合、suffixes を « suffixes » に設定します。

        4. suffixes の各 suffix について繰り返します:

          1. filename が suffix で終わる場合、true を返します。

      3. false を返します。

    3. description を type["description"] とします。

    4. description が空の文字列の場合、 description を filter を説明するユーザー理解可能な文字列に設定します。

    5. (description, filter) を accepts options に追加します。

  3. accepts options が空であるか、 または options["excludeAcceptAllOption"] が false のいずれかの場合:

    1. description を「すべてのファイル」を説明するユーザー理解可能な文字列とします。

      1. filter を true を返すアルゴリズムとします。

      2. (description, filter) を accepts options に追加します。

  4. accepts options が空の場合、throw a TypeError.

  5. accepts options を返します。

suffix を検証する suffix には、次の手順を実行します:
  1. suffix が "." で始まらない場合、 throw a TypeError.

  2. suffix にコードポイント が含まれていない有効なサフィックスコードポイントの場合、throw a TypeError.

  3. suffix が "." で終わる場合、throw a TypeError.

  4. suffix の length が 16より大きい場合、 throw a TypeError.

3.2.2. 開始ディレクトリ

id および startIn フィールドを指定して、 ファイルピッカーが開くディレクトリを提案できます。

これらのオプションのいずれも指定されていない場合、ユーザーエージェントは最後にファイルまたはディレクトリが選択されたディレクトリを記憶し、新しいピッカーはそのディレクトリから開始されます。 id を指定すると、ユーザーエージェントは異なるIDに対して異なるディレクトリを記憶できます (ユーザーエージェントは限られた数のIDに対してのみディレクトリを記憶します)。

// このIDから以前に選択されたディレクトリへのマッピングが存在する場合、この
// ディレクトリから開始します。それ以外の場合、このIDからこのファイルピッカー
// 呼び出しの結果であるディレクトリへのマッピングが作成されます。
const options = {
  id: 'foo',
};

startIn を FileSystemFileHandle として指定すると、 ダイアログはそのファイルの親ディレクトリから開始されます。一方、 FileSystemDirectoryHandle を渡すと、 ダイアログは渡されたディレクトリから開始されます。これらは、明示的な id も渡された場合でも優先されます。

たとえば、FileSystemDirectoryHandle project_dir が与えられた場合、次のようにして そのディレクトリから開始するファイルピッカーを表示します:

// |project_dir| のディレクトリにピッカーが開きます。'foo' が有効なマッピング
// を持っているかどうかに関係なく。
const options = {
  id: 'foo',
  startIn: |project_dir|,
};

id および startIn フィールドは、 ピッカーが開くディレクトリのみを制御します。上記の例では、 ファイルピッカーの操作が完了した後に、id 'foo' が project_dir と同じディレクトリにマップされることを仮定できません。

startIn を WellKnownDirectory として指定すると、 ダイアログは明示的な id が渡された場合でも、 そのディレクトリから開始されますが、有効なディレクトリへのマッピングがある場合を除きます。

以下は、id と startIn を WellKnownDirectory として指定する例です。 指定されたIDから有効なパスへの既存のマッピングが存在する場合、そのマッピングが使用されます。それ以外の場合、WellKnownDirectory 経由で提案されたパスが使用されます。

// 'foo' のIDを初めて指定します。ディレクトリにマップされていません。
// ファイルピッカーはDownloadsディレクトリから開くようにフォールバックします。TODO: link this.
const options = {
  id: 'foo',  // 未マップ。
  startIn: "downloads",  // ここから開始。
};

// 後で...

// 'foo' のIDはマップされている場合とされていない場合があります。例えば、このIDの
// マッピングはエビクションされている可能性があります。
const options = {
  id: 'foo',  // マップされている可能性あり。もしそうなら、ここから開始。
  startIn: "downloads",  // それ以外の場合、ここから開始。
};

startIn および id オプションは Chrome 91 で初めて導入されました。

ユーザーエージェントは、最近選択されたディレクトリマップを保持します。これは、 順序付きマップ のオリジンからパスIDマップへのものです。

パスIDマップは順序付きマップ で、有効なパスIDからパスへのものです。

有効なパスIDは文字列で、 各文字がASCII英数字または "_" または "-" です。

パスIDマップが無制限に成長しないように、 ユーザーエージェントは最近選択されたディレクトリを記憶する数を制限する 何らかのメカニズムを実装すべきです。これはたとえば、最も最近使用されていないエントリをエビクションすることで行うことができます。 ユーザーエージェントは、パスIDマップに少なくとも16エントリを格納できるようにすべきです。

WellKnownDirectory enum により、ウェブサイトは いくつかの既知のディレクトリの中から1つを選択できます。これらの値がマップする正確なパスは実装定義 (一部の場合、これらはディスク上の実際のパスを表さない場合もあります)。 次のリストでは、各値の意味を説明し、異なるオペレーティングシステムでの可能な例のパスを示します:

"desktop"

ユーザーのデスクトップディレクトリ(存在する場合)。たとえばこれは C:\Documents and Settings\username\Desktop、/Users/username/Desktop、または /home/username/Desktopである可能性があります。

"documents"

ユーザーが作成したドキュメントが通常保存されるディレクトリ。 たとえば C:\Documents and Settings\username\My Documents、 /Users/username/Documents、または /home/username/Documents。

"downloads"

ダウンロードされたファイルが通常保存されるディレクトリ。 たとえば C:\Documents and Settings\username\Downloads、 /Users/username/Downloads、または /home/username/Downloads。

"music"

オーディオファイルが通常保存されるディレクトリ。 たとえば C:\Documents and Settings\username\My Documents\My Music、 /Users/username/Music、または /home/username/Music。

"pictures"

写真や他の静止画像が通常保存されるディレクトリ。 たとえば C:\Documents and Settings\username\My Documents\My Pictures、 /Users/username/Pictures、または /home/username/Pictures。

"videos"

ビデオ/ムービーが通常保存されるディレクトリ。 たとえば C:\Documents and Settings\username\My Documents\My Videos、 /Users/username/Movies、または /home/username/Videos。

ピッカーが開始するディレクトリを決定するには、オプションの文字列 id、 オプションのStartInDirectory startIn および環境設定オブジェクト environment を与えて、次の手順を実行します:
  1. id が与えられていて、有効なパスIDでない場合、 throw a TypeError.

  2. id のlength が32より大きい場合、 throw a TypeError.

  3. origin を environment のorigin とします。

  4. startIn がFileSystemHandle で、startIn が bucketファイルシステム内でない場合:

    1. entry を、locating an entry を startIn のlocator で実行した結果とします。

    2. entry がfile entry の場合、ローカルファイルシステムで entry のparent のパスを返します。

    3. entry がdirectory entry の場合、ローカルファイルシステムで entry のパスを返します。

  5. id が空でない場合:

    1. recently picked directory map[origin] が存在する場合:

      1. path map をrecently picked directory map[origin] とします。

      2. path map[id] が存在する場合、path map[id] を返します。

  6. startIn がWellKnownDirectory の場合:

    1. startIn のWellKnownDirectory 値に対応するユーザーエージェント定義のパスを返します。

  7. id が指定されていない、または空の文字列の場合:

    1. recently picked directory map[origin] が存在する場合:

      1. path map をrecently picked directory map[origin] とします。

      2. path map[""] が存在する場合、path map[""] を返します。

  8. ユーザーエージェント固有の方法でデフォルトパスを返します。

選択されたディレクトリを記憶するには、オプションの文字列 id、 ファイルシステムエントリ entry、および環境設定オブジェクト environment を与えて、次の手順を実行します:
  1. origin を environment のorigin とします。

  2. recently picked directory map[origin] が存在しない場合、 recently picked directory map[origin] を空のパスIDマップに設定します。

  3. id が指定されていない場合、id を空の文字列とします。

  4. recently picked directory map[origin][id] を、ローカルファイルシステムで entry に対応するパスに設定します、 そのようなパスが決定できる場合。

3.3. showOpenFilePicker() メソッド

[ handle ] = await window . showOpenFilePicker()
[ handle ] = await window . showOpenFilePicker({ multiple: false })

ユーザーが単一の既存ファイルを選択できるファイルピッカーを表示し、選択されたファイルのハンドルを返します。

handles = await window . showOpenFilePicker({ multiple: true })

ユーザーが複数の既存ファイルを選択できるファイルピッカーを表示し、選択されたファイルのハンドルを返します。

showOpenFilePicker() に追加オプションを渡して、 ウェブサイトがユーザーに選択させるファイルのタイプと、 ファイルピッカーが開くディレクトリを示すことができます。詳細は§ 3.2 File picker options を参照してください。

showOpenFilePicker(options) メソッドは、呼び出されたときに これらの手順を実行します:
  1. environment をthisの関連設定オブジェクトとします。

  2. accepts options をaccept types を処理する options で実行した結果とします。

  3. starting directory を、 ピッカーが開始するディレクトリを決定する を options["id"], options["startIn"], および environment で実行した結果とします。

  4. global を environment のglobal object とします。

  5. environment がファイルピッカーを表示することを許可されているかを検証します。

  6. p を新しいプロミスとします。

  7. 次の手順を並列で実行します:

    1. オプションで、このアルゴリズムの以前の実行が終了するまで待機します。

    2. filePickerOptions を空の順序付きマップとします。

    3. filePickerOptions["multiple"] を options["multiple"] に設定します。

    4. dismissed をWebDriver BiDi file dialog opened を null と filePickerOptions で実行した結果とします。

    5. dismissed が false の場合:

      1. ユーザーにいくつかのファイルをピックすることを要求するプロンプトを表示します。 filePickerOptions["multiple"] が false の場合、選択されるファイルは1つ以下でなければなりません; それ以外の場合、任意の数を選択できます。

        表示されたプロンプトは、表示されるファイルのリストをフィルタリングするための accepts options のいずれかをユーザーが選択できるようにします。 これがどのように実装され、このプロンプトがどのように見えるかは実装定義です。

        可能であれば、このプロンプトは starting directory を表示して開始します。

      2. ユーザーが選択を行うまで待機します。

    6. dismissed が true である、またはユーザーが選択を行わずにプロンプトを却下した場合、 reject p を "AbortError" DOMException で行い、中断します。

    7. entries を、選択されたファイルまたはディレクトリを表すリストのファイルエントリとします。

    8. result を空のリストとします。

    9. entries の各 entry について繰り返します:

      1. entry がユーザーエージェントによってこのウェブサイトに公開するのがあまりにも敏感または危険と見なされる場合:

        1. 選択されたファイルまたはディレクトリをこのウェブサイトに公開できないことをユーザーに通知します。

        2. ユーザーエージェントの裁量で、 これらの並列手順の先頭に戻るか、 reject p を "AbortError" DOMException で行い、中断します。

      2. entry に関連付けられた新しいFileSystemFileHandle を result に追加します。

    10. 選択されたディレクトリを記憶する を options["id"], entries[0] および environment で実行します。

    11. global のbrowsing context でactivation notification 手順を実行します。

      注意: これにより、ウェブサイトはすぐに ユーザーアクティベーションを必要とする操作(追加の権限を要求するようなもの)を実行できます。

    12. resolve p を result で行います。

  8. p を返します。

3.4. showSaveFilePicker() メソッド

handle = await window . showSaveFilePicker( options )

ユーザーが単一のファイルを選択できるファイルピッカーを表示し、選択されたファイルのハンドルを返します。選択されたファイルは既に存在する必要はありません。選択されたファイルが存在しない場合、このメソッドが返る前に新しい空のファイルが作成され、それ以外の場合は既存のファイルがこのメソッドが返る前にクリアされます。

handle = await window . showSaveFilePicker({ suggestedName: "README.md" })

"README.md" という提案ファイル名がデフォルトの保存ファイル名として事前入力されたファイルピッカーを表示します。

追加options を showSaveFilePicker() に渡して、 ウェブサイトがユーザーに選択させるファイルのタイプと、 ファイルピッカーが開くディレクトリを示すことができます。 § 3.2 File picker options を参照してください。

suggestedName オプションは Chrome 91 で初めて導入されました。

showSaveFilePicker(options) メソッドは、 呼び出されたときに、これらの手順を実行しなければなりません:
  1. environment を this の 関連設定オブジェクト とします。

  2. accepts options を accept types を処理する options で実行した結果とします。

  3. starting directory を、 ピッカーが開始するディレクトリを決定する を options["id"], options["startIn"] および environment で実行した結果とします。

  4. global を environment の global object とします。

  5. environment が ファイルピッカーを表示することを許可されているかを検証します。

  6. p を 新しいプロミス とします。

  7. 次の手順を 並列で実行します:

    1. オプションで、このアルゴリズムの以前の実行が終了するまで待機します。

    2. ユーザーに正確に1つのファイルを選択するよう要求するプロンプトを表示します。 表示されたプロンプトは、表示されるファイルのリストをフィルタリングするための accepts options のいずれかをユーザーが選択できるようにします。 これがどのように実装され、このプロンプトがどのように見えるかは 実装定義です。 accepts options がUIに表示される場合、選択されたオプションは ユーザー提供のファイル名に追加する拡張子を提案するためにも使用されますが、これは必須ではありません。特にユーザーエージェントは ".lnk" や ".local" で終わるような潜在的に危険なサフィックスを無視する自由があります。

      可能であれば、このプロンプトは starting directory を表示して開始します。

      options["suggestedName"] が 存在 し、 null でない場合、ファイルピッカープロンプトは options["suggestedName"] をデフォルトの保存名として事前入力されます。 suggestedName と accepts options の相互作用は 実装定義です。 suggestedName があまりにも危険と見なされる場合、ユーザーエージェントは提案されたファイル名を無視またはサニタイズすべきです、ダウンロードとしてフェッチするときに行われるサニタイズと同様です。

      注意: ユーザーエージェントはたとえば accepts options で suggestedName と一致するオプションをデフォルトフィルタとして選択できます。

    3. ユーザーが選択を行うまで待機します。

    4. ユーザーが選択を行わずにプロンプトを却下した場合、 reject p を "AbortError" DOMException で行い、中断します。

    5. entry を、選択されたファイルを表す file entry とします。

    6. entry がユーザーエージェントによってこのウェブサイトに公開するのが あまりにも敏感または危険 と見なされる場合:

      1. 選択されたファイルまたはディレクトリをこのウェブサイトに公開できないことをユーザーに通知します。

      2. ユーザーエージェントの裁量で、 これらの 並列 手順の先頭に戻るか、 reject p を "AbortError" DOMException で行い、中断します。

    7. entry の binary data を空の byte sequence に設定します。

    8. result を entry に関連付けられた新しい FileSystemFileHandle に設定します。

    9. 選択されたディレクトリを記憶する を options["id"], entry および environment で実行します。

    10. global の browsing context で activation notification 手順を実行します。

      注意: これにより、ウェブサイトはすぐに 返されたハンドルに対する操作(追加の権限を要求するようなもの)を実行できます。

    11. Resolve p を result で行います。

  8. p を返します。

3.5. showDirectoryPicker() メソッド

handle = await window . showDirectoryPicker()
handle = await window . showDirectoryPicker()({ mode: 'read' })

ユーザーが単一のディレクトリを選択できるディレクトリピッカーを表示し、ユーザーが読み取り権限を付与した場合、選択されたディレクトリのハンドルを返します。

handle = await window . showDirectoryPicker()({ mode: 'readwrite' })

ユーザーが単一のディレクトリを選択できるディレクトリピッカーを表示し、選択されたディレクトリのハンドルを返します。ユーザーエージェントはこのハンドルに対する読み取りと書き込み権限のリクエストを1つの後続プロンプトに組み合わせることができます。

id および startIn フィールドは、 id および startIn フィールドとそれぞれ同一です。 これらのフィールドを使用する方法の詳細については § 3.2.2 Starting Directory を参照してください。

showDirectoryPicker(options) メソッドは、呼び出されたときに、次の手順を実行しなければなりません:
  1. environment を this の 関連設定オブジェクト とします。

  2. starting directory を ピッカーが開始するディレクトリを決定する を options["id"], options["startIn"] および environment で実行した結果とします。

  3. global を environment の global object とします。

  4. environment が ファイルピッカーを表示することを許可されているかを検証します。

  5. p を 新しいプロミス とします。

  6. 次の手順を 並列で実行します:

    1. オプションで、このアルゴリズムの以前の実行が終了するまで待機します。

    2. filePickerOptions を空の map とします。

    3. filePickerOptions["multiple"] を false に設定します。

    4. dismissed を WebDriver BiDi file dialog opened を null と filePickerOptions で実行した結果とします。

    5. dismissed が false の場合:

      1. ユーザーにディレクトリを選択するよう要求するプロンプトを表示します。

        可能であれば、このプロンプトは starting directory を表示して開始します。

      2. ユーザーが選択を行うまで待機します。

    6. dismissed が true である、またはユーザーが選択を行わずにプロンプトを却下した場合、 reject p を "AbortError" DOMException で行い、中断します。

    7. entry を、選択されたディレクトリを表す directory entry とします。

    8. entry がユーザーエージェントによってこのウェブサイトに公開するのが あまりにも敏感または危険 と見なされる場合:

      1. 選択されたファイルまたはディレクトリをこのウェブサイトに公開できないことをユーザーに通知します。

      2. ユーザーエージェントの裁量で、 これらの 並列 手順の先頭に戻るか、 reject p を "AbortError" DOMException で行い、中断します。

    9. result を entry に関連付けられた新しい FileSystemDirectoryHandle に設定します。

    10. 選択されたディレクトリを記憶する を options["id"], entry および environment で実行します。

    11. desc を FileSystemPermissionDescriptor とします。

    12. desc["name"] を "file-system" に設定します。

    13. desc["handle"] を result に設定します。

    14. desc["mode"] を options["mode"] に設定します。

    15. status を desc の create a PermissionStatus を実行した結果とします。

    16. global の browsing context で activation notification 手順を実行します。

    17. desc を使用する permission を要求する を実行します。

    18. desc と status で default permission query algorithm を実行します。

    19. status が "granted" でない場合、 reject result を "AbortError" DOMException で行い、中断します。

    20. global の browsing context で activation notification 手順を実行します。

    21. Resolve p を result で行います。

  7. p を返します。

3.6. ドラッグアンドドロップ

partial interface DataTransferItem {
    Promise<FileSystemHandle?> getAsFileSystemHandle();
};

ドラッグアンドドロップ操作中に、ドラッグされたファイルおよび ディレクトリアイテムは、それぞれファイルエントリおよびディレクトリエントリ に関連付けられます。

handle = await item . getAsFileSystemHandle()

ドラッグされたアイテムがファイルの場合、FileSystemFileHandle オブジェクトを返し、ドラッグされたアイテムがディレクトリの場合、FileSystemDirectoryHandle オブジェクトを返します。

getAsFileSystemHandle() メソッドのステップは以下の通りです:

  1. DataTransferItem オブジェクトが読み書きモードまたは読み取り専用モードでない場合、nullで解決された約束を返します。

  2. ドラッグデータアイテムの種類がFileでない場合、 nullで解決された約束を返します。

  3. pを新しい約束とします。

  4. 以下のステップを並行して実行します:

    1. entryをドラッグされたファイルまたはディレクトリを表すファイルシステムエントリとします。

    2. entryがファイルエントリの場合:

      1. handleをentryに関連付けられたFileSystemFileHandle とします。

    3. それ以外の場合は、entryがディレクトリエントリの場合:

      1. handleをentryに関連付けられたFileSystemDirectoryHandle とします。

    4. pをentryで解決します。

  5. pを返します。

ファイルとディレクトリのドラッグアンドドロップの処理:
elem.addEventListener('dragover', (e) => {
  // ナビゲーションを防ぐ。
  e.preventDefault();
});

elem.addEventListener('drop', async (e) => {
  e.preventDefault();

  const fileHandlesPromises = [...e.dataTransfer.items]
    .filter(item => item.kind === 'file')
    .map(item => item.getAsFileSystemHandle());

  for await (const handle of fileHandlesPromises) {
    if (handle.kind === 'directory') {
      console.log(`Directory: ${handle.name}`);
    } else {
      console.log(`File: ${handle.name}`);
    }
  }
});

これは現在、過度に敏感または危険な ディレクトリへのアクセスをブロックしていません。 これは、ドロップされたファイルとディレクトリにアクセスを与える他のAPIとの一貫性を保つためです。しかし、これはローカルファイルシステムハンドルファクトリとは一貫性がありません。そのため、これを再検討する可能性があります。

4. アクセシビリティに関する考慮事項

このセクションは非規範的です。

この仕様がユーザーインターフェースに情報を提示する場合、 実装者はプラットフォームのOSレベルのアクセシビリティガイドラインに従うべきです。

5. プライバシーに関する考慮事項

このセクションは非規範的です。

このAPIは、既存の <input type=file> および<input type=file webkitdirectory> APIがすでに持っているデータへの読み取りアクセスをウェブサイトに与える以上のものではありません。さらに、これらのAPIと同様に、 ファイルおよびディレクトリへのすべてのアクセスは、ファイルまたはディレクトリピッカーの背後に明示的にゲートされています。

しかし、この新しいAPIにはいくつかの主要なプライバシーリスクがあります:

5.1. ユーザーが意図したよりも多くの、またはより敏感なファイルへのアクセスを与える。

これはこのAPIの新しいリスクではありませんが、ユーザーがウェブサイトがアクセスしているものを正確に認識できるように、ユーザーエージェントは努めるべきです。これは、 ユーザーがディレクトリに実際にどれだけのファイルが存在するかをすぐに明確にしない場合に、ディレクトリへのアクセスを与える場合に特に重要です。

関連するリスクは、ユーザーが特に敏感なデータを与えることです。 これには、ユーザーエージェント自身の設定データ、ネットワークキャッシュやクッキーストア、 またはパスワードファイルなどのオペレーティングシステム設定データが含まれる可能性があります。これを保護するために、ユーザーエージェントはディレクトリピッカーでユーザーが選択できるディレクトリを制限することを奨励され、 潜在的にユーザーが特に敏感なデータを含むディレクトリへのアクセスを与えるのを難しくするために、選択できるファイルも制限します。正しいバランスを維持することが重要です。結局のところ、このAPIはユーザーが最もプライベートな個人データをウェブサイトとやり取りできるように意図的にしています。

ユーザーエージェントが制限することを望むディレクトリの例として、 過度に敏感または危険なディレクトリには以下が含まれます:

5.2. ウェブサイトがこのAPIを追跡に使用しようとする。

このAPIは、ブラウジングデータをクリアしてもユーザー間でユーザーを追跡するために使用される可能性があります。これは、 既存のファイルアクセスAPIとは異なり、ユーザーエージェントがファイルまたはディレクトリへの永続アクセスを許可でき、再プロンプトできるためです。 ファイルへの書き込み機能と組み合わせると、ウェブサイトはユーザーのディスク上に識別子を永続化でき、 これらの識別子はブラウジングデータをクリアしても影響を受けません。

このリスクは、ブラウジングデータをクリアしてもウェブサイトが永続化したハンドル(たとえばIndexedDBで)をクリアするという事実によってある程度軽減されます。 さらに、ユーザーエージェントはウェブサイトがアクセスしているファイルとディレクトリを明確にし、 特に信頼できる起源に対してのみ永続的な許可付与を制限するよう奨励されます。

ユーザーエージェントはまた、ユーザーが付与された許可を撤回する方法を提供することを奨励されます。 ブラウジングデータをクリアすると、すべての許可が撤回されることが期待されます。

5.3. ファーストパーティ対サードパーティのコンテキスト。

サードパーティコンテキスト(たとえば、トップレベルのフレームのオリジンと一致しないiframe内)で、 ウェブサイトはすでにアクセスしているデータにアクセスすることはできません。これには、ローカルファイルシステムハンドルファクトリを介した新しいファイルまたはディレクトリへのアクセスの取得、 および既存のハンドルへのrequestPermission APIを介したより多くの許可のリクエストが含まれます。

ハンドルは同じオリジンの宛先にのみポストメッセージできます。ハンドルをクロスオリジン宛先に送信しようとすると、 messageerror イベントが発生します。

6. セキュリティに関する考慮事項

このセクションは非規範的です。

このAPIは、ディスク上の既存のファイルを変更するだけでなく、新しいファイルに書き込む機能をウェブサイトに与えます。これにはいくつかの重要なセキュリティ考慮事項があります:

6.1. マルウェア

このAPIは、ウェブサイトがユーザーのシステムにマルウェアを保存または実行しようとするために使用される可能性があります。これを軽減するために、このAPIはファイルを実行可能としてマークする方法を提供しません(ただし、 すでに実行可能なファイルは、このAPIを通じて変更された後もそのようになる可能性があります)。さらに、ユーザーエージェントはこのAPIによって作成または変更されたファイルにMark-of-the-Webを適用することを奨励されます。

最後に、ユーザーエージェントはこのAPIによって変更されたファイルの内容をマルウェアスキャンおよびセーフブラウジングチェックで検証することを奨励されます、 ただし、いくつかの外部の強力な信頼関係がすでに存在する場合を除きます。もちろん、これはこのAPIのパフォーマンス特性に影響を与えます。

6.2. ランサムウェア攻撃

ランサムウェア攻撃のもう一つのリスク要因は、上記の特定の敏感なディレクトリへのアクセスをブロックするという制限によって軽減されます。さらに、ユーザーエージェントはファイルへの書き込みアクセスを適切とみなす粒度で許可できます。

6.3. ユーザーのディスクを埋める

バケットファイルシステムのファイル以外、このAPIによって書き込まれたファイルはストレージクォータの対象ではありません。その結果、ウェブサイトはクォータによって制限されることなくユーザーのディスクを埋めることができます(ただし、 ストレージクォータの対象となるストレージであっても、ユーザーのディスクを埋めたり、ほぼ埋めたりすることはまだ可能です。なぜならストレージクォータは一般的に利用可能なディスクスペースに依存しないからです)。

このAPIなしで、ウェブサイトは大規模なファイルをトリガーしてダウンロードすることで(潜在的にクライアント側で作成され、 ネットワークオーバーヘッドを発生させない)、クォータ制限の対象となるストレージにデータを書き込むことができます。truncate() の存在とファイルの終わりを超えた非常に大きなオフセットでの書き込みにより、大規模なファイルを作成することがはるかに簡単で低コストになります。ただし、 ほとんどのファイルシステムはスパースファイルをサポートしているため、これらのファイルは実際に生成されたNULバイトを保存しない場合があります。

ウェブサイトがこのAPIを使用してディスクに書き込む場合、クォータ管理ストレージまたは既存のダウンロードメカニズムを介してウェブサイトがユーザーのディスクを埋めるのを防ぐためにユーザーエージェントが使用するあらゆる緩和策も使用されるべきです。

適合性

文書の表記規則

適合性要件は、 記述的なアサーションとRFC 2119の用語の組み合わせで表現されます。 この仕様の規範的な部分における「MUST」、「MUST NOT」、「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、「SHOULD NOT」、「RECOMMENDED」、「MAY」、「OPTIONAL」という重要な単語は、 RFC 2119で説明されているように解釈されます。 ただし、この仕様では読みやすさのために、 これらの単語はすべて大文字で表示されていません。

この仕様のすべてのテキストは規範的です 例、ノートなど、明示的に非規範的とマークされたセクションを除きます。[RFC2119]

この仕様の例は、「for example」という単語で導入されるか、 規範的なテキストからclass="example"で分離されます、 次のように:

これは有益な例の例です。

有益なノートは「Note」という単語で始まり、 規範的なテキストからclass="note"で分離されます、 次のように:

注、これは有益なノートです。

索引

この仕様で定義された用語

参照によって定義される用語

参照文献

規範的参照文献

[FS]
Austin Sullivan. File System Standard. Living Standard. URL: https://fs.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[MIMESNIFF]
Gordon P. Hemsley. MIME Sniffing Standard. Living Standard. URL: https://mimesniff.spec.whatwg.org/
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[PERMISSIONS-REQUEST]
Requesting Permissions. Draft Community Group Report. URL: https://wicg.github.io/permissions-request/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[STORAGE]
Anne van Kesteren. Storage Standard. Living Standard. URL: https://storage.spec.whatwg.org/
[WEBDRIVER-BIDI]
James Graham; Alex Rudenko; Maksim Sadym. WebDriver BiDi. URL: https://w3c.github.io/webdriver-bidi/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

有益な参照文献

[ENTRIES-API]
File and Directory Entries API. Draft Community Group Report. URL: https://wicg.github.io/entries-api/
[FILE-API]
Marijn Kruisselbrink. File API. URL: https://w3c.github.io/FileAPI/

IDLインデックス

enum FileSystemPermissionMode {
  "read",
  "readwrite"
};

dictionary FileSystemPermissionDescriptor : PermissionDescriptor {
  required FileSystemHandle handle;
  FileSystemPermissionMode mode = "read";
};

dictionary FileSystemHandlePermissionDescriptor {
  FileSystemPermissionMode mode = "read";
};

[Exposed=(Window,Worker), SecureContext, Serializable]
partial interface FileSystemHandle {
  Promise<PermissionState> queryPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
  Promise<PermissionState> requestPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
};

enum WellKnownDirectory {
  "desktop",
  "documents",
  "downloads",
  "music",
  "pictures",
  "videos",
};

typedef (WellKnownDirectory or FileSystemHandle) StartInDirectory;

dictionary FilePickerAcceptType {
    USVString description = "";
    record<USVString, (USVString or sequence<USVString>)> accept;
};

dictionary FilePickerOptions {
    sequence<FilePickerAcceptType> types;
    boolean excludeAcceptAllOption = false;
    DOMString id;
    StartInDirectory startIn;
};

dictionary OpenFilePickerOptions : FilePickerOptions {
    boolean multiple = false;
};

dictionary SaveFilePickerOptions : FilePickerOptions {
    USVString? suggestedName;
};

dictionary DirectoryPickerOptions {
    DOMString id;
    StartInDirectory startIn;
    FileSystemPermissionMode mode = "read";
};

[SecureContext]
partial interface Window {
    Promise<sequence<FileSystemFileHandle>> showOpenFilePicker(optional OpenFilePickerOptions options = {});
    Promise<FileSystemFileHandle> showSaveFilePicker(optional SaveFilePickerOptions options = {});
    Promise<FileSystemDirectoryHandle> showDirectoryPicker(optional DirectoryPickerOptions options = {});
};

partial interface DataTransferItem {
    Promise<FileSystemHandle?> getAsFileSystemHandle();
};

問題インデックス

これらのチェックをエントリに関連付けないようにします。 [whatwg/fs Issue #101] ↵
理想的には、このユーザーアクティベーション要件は上流で定義されるでしょう。 [WICG/permissions-request Issue #2] ↵
現在FileSystemPermissionMode は "read" または "readwrite" のみです。 将来的には、書き込み専用のハンドルをサポートするために "write" モードも追加したいかもしれません。 [Issue #119] ↵
これは現在、過度に敏感または危険な ディレクトリへのアクセスをブロックしていません、 ドロップされたファイルとディレクトリにアクセスを与える他のAPIとの一貫性を保つためです。これはローカルファイルシステムハンドルファクトリとは一貫性がありません、 なので、これを再検討する可能性があります。 ↵
⚠MDN

DataTransferItem/getAsFileSystemHandle

In only one current engine.

FirefoxNoneSafariNoneChrome86+
Opera?Edge86+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for AndroidNoneAndroid WebView?Samsung Internet?Opera Mobile?
⚠MDN

FileSystemHandle/queryPermission

In only one current engine.

FirefoxNoneSafariNoneChrome86+
Opera?Edge86+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
⚠MDN

FileSystemHandle/requestPermission

In only one current engine.

FirefoxNoneSafariNoneChrome86+
Opera?Edge86+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
⚠MDN

window/showdirectorypicker

In only one current engine.

FirefoxNoneSafariNoneChrome86+
Opera?Edge86+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for AndroidNoneAndroid WebView?Samsung Internet?Opera Mobile?
⚠MDN

window/showopenfilepicker

In only one current engine.

FirefoxNoneSafariNoneChrome86+
Opera?Edge86+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for AndroidNoneAndroid WebView?Samsung Internet?Opera Mobile?
⚠MDN

window/showsavefilepicker

In only one current engine.

FirefoxNoneSafariNoneChrome86+
Opera?Edge86+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for AndroidNoneAndroid WebView?Samsung Internet?Opera Mobile?