1. 目的
この仕様は、ファイルとディレクトリの階層がページにドラッグ&ドロップされたり、フォーム要素を使って選択されたり、またはそれに類するユーザー操作が行われたとき、ウェブブラウザーがスクリプトに対して利用可能にする型と操作について記述します。
これは、[file-system-api] の初期ドラフトを大きく基にしており、サンドボックス化されたファイルシステムの文脈で同様の型と、ファイルやディレクトリの作成・修正のための操作を定義していますが、ウェブブラウザーによる広範な採用には至っていません。
注: この文書で記述されているAPIは、当初Google Chromeで実装されました。他のブラウザー(現時点ではEdge、Firefox、Safari)はChromeのAPIと挙動の一部をサポートし始めています。本書の目的は、実装間の互換性を確保するため、共通部分を仕様化することです。
2. 概念
2.1. 名前とパス
名前は、次の条件を満たす文字列です:
-
'/' (U+002F SOLIDUS) を含まない
-
NUL (U+0000) を含まない
-
'\' (U+005C REVERSE SOLIDUS) を含まない
-
'.' (U+002E FULL STOP) ではない
-
'..' (U+002E FULL STOP, U+002E FULL STOP) ではない
パスセグメントは、名前、'.'(U+002E FULL STOP)、または '..'(U+002E FULL STOP, U+002E FULL STOP) のいずれかです。
相対パスは、1つ以上の パスセグメント が '/' (U+002F SOLIDUS) で結合された文字列で、先頭が '/' (U+002F SOLIDUS) で始まらないものです。
絶対パスは、'/' (U+002F SOLIDUS) の後に0個以上の パスセグメント が '/' (U+002F SOLIDUS) で結合された文字列です。
有効なパスは、USVString
であり、パスであるものです。
2.2. ファイルとディレクトリ
ファイルは、バイナリデータと、名前(非空の名前)から構成されます。
ディレクトリは、名前(名前)と、順序付きメンバーリストから構成されます。各メンバーはファイルまたはディレクトリのいずれかです。ディレクトリの各メンバーは、異なる非空の名前を持たなければなりません。
ルートディレクトリは、ディレクトリであり、他のディレクトリのメンバーではありません。ルートディレクトリの名前は空です。
親は、ファイルまたはディレクトリの所属するディレクトリです。ルートディレクトリには親がありません。
注: 多くの場合、ユーザーが選択したファイルとディレクトリは、APIによって「仮想ルート」に含まれているかのように提示されますが、この仮想ルートは実際のネイティブファイルシステム上には実体として存在しません。
ファイルシステムは、名前と、ルート(関連付けられたルートディレクトリ)から構成されます。ファイルシステムの名前は、USVString
であり、実装依存ですが、そのファイルシステムに固有です。ルートディレクトリは、1つのファイルシステムにだけ関連付けられます。
注: 実装では、各名前を、各ファイルシステムインスタンスごとにUUIDを生成し、固定のプレフィックスやサフィックス文字列を付加するなどで作成しても構いません。API利用者は、名前の構造や内容について仮定しないことが推奨されます。
2.3. エントリ
エントリは、ファイルエントリまたはディレクトリエントリのいずれかです。
エントリは、名前(名前)およびフルパス(絶対パス)を持ちます。
エントリは、さらにルート(関連付けられたルートディレクトリ)を持ちます。
注: エントリは、ルートディレクトリを基準としたパスで定義されており、APIとのやりとりの背後にあるネイティブファイルシステムがディレクトリ内容の列挙などの操作中に非同期的に変更される可能性があることを考慮しています。エントリに対して公開される操作は、パスが同じエンティティを参照しなくなった場合、エラーとなります。
ファイルシステムは、エントリが関連付けられているファイルシステムであり、エントリのルートに関連付けられています。
3. アルゴリズム
abspath(絶対パス)と path(絶対パス、相対パス、または空文字列)を使って相対パスを解決するには、次の手順を実行します。これらは絶対パスを返します。
-
path が 絶対パスなら、path を返す。
-
abspath segments を ‘/’(U+002F SOLIDUS)で厳密分割した結果にする。
注: 最初の文字列は空になります。
-
path segments を ‘/’(U+002F SOLIDUS)で厳密分割した結果にする。
-
path segments の各 segment について、segment で分岐:
- 空文字列
-
継続する。
- '.' (U+002E FULL STOP)
-
継続する。
- '..' (U+002E FULL STOP, U+002E FULL STOP)
-
abspath segments の最後のメンバーを削除。ただし、唯一のメンバーの場合は削除しない。
- その他
-
segment を abspath segments に追加する。
-
abspath segments を '/' (U+002F SOLIDUS) で結合して返す。
directory(ルートディレクトリ)と path(絶対パス)を使ってパスを評価するには、次の手順を実行します。これらはファイル、ディレクトリ、または失敗を返します。
-
segments を ‘/’(U+002F SOLIDUS)で厳密分割した結果にする。
-
segments の最初の要素を削除する。
注: path が 絶対パスなので、最初の要素は必ず空です。
-
segments の各 segment について、segment で分岐:
4. File
インターフェース
partial interface File {readonly attribute USVString webkitRelativePath ; };
webkitRelativePath
ゲッターの手順は、this の 相対パスを返すか、指定されていなければ空文字列を返します。
5. HTML:フォーム
partial interface HTMLInputElement {attribute boolean ;
webkitdirectory readonly attribute FrozenArray <FileSystemEntry >; };
webkitEntries
input
要素の
type
属性が ファイルアップロード状態のとき、このセクションのルールが適用されます。
webkitdirectory
属性は、ユーザーがファイルやファイル群ではなくディレクトリを選択できるかどうかを示すブール属性です。指定された場合、ディレクトリが選択されると、そのディレクトリを祖先にもつすべてのファイルが選択されたかのように動作します。さらに、各webkitRelativePath
プロパティは、選択されたディレクトリからファイルへの相対パス(ディレクトリ自身も含む)に設定されます。
documents/ to_upload/ a/ b/ 1.txt 2.txt 3.txt not_uploaded.txt
to_upload
ディレクトリが選択された場合、files
には以下が含まれます:
-
name
== "1.txt
" かつwebkitRelativePath
== "to_upload/a/b/1.txt
" -
name
== "2.txt
" かつwebkitRelativePath
== "to_upload/a/b/2.txt
" -
name
== "3.txt
" かつwebkitRelativePath
== "to_upload/a/3.txt
"
注:
ユーザーエージェントは、選択操作時に任意の階層データをディレクトリとして表現できます。例えば、ネイティブファイルシステムを直接ユーザーに公開しないデバイスでは、"image/*"
が
accept
属性に指定されていれば、写真アルバムをディレクトリとして提示することができます。
webkitRelativePath
プロパティを、input
要素でディレクトリ選択した後に確認する例:
< input id = b type = file webkitdirectory >
document. querySelector( '#b' ). addEventListener( 'change' , e=> { for ( file entryof e. target. files) { console. log( file. name, file. webkitRelativePath); } });
webkitEntries
IDL属性は、スクリプトが要素の選択されたエントリにアクセスできるようにします。取得時、このIDL属性が適用される場合は現在の選択されたファイル(ディレクトリ含む)を表すFileSystemEntry
オブジェクトの配列を返さなければなりません。適用されない場合は
null を返します。
webkitEntries
でエントリを列挙する例:
< input id = a type = file multiple >
document. querySelector( '#a' ). addEventListener( 'change' , e=> { for ( const entryof e. target. webkitEntries) { handleEntry( entry); } });
webkitEntries
はドラッグ&ドロップ操作の結果のみが設定されます。要素をクリックした場合は設定されません。常に設定されるように修正すべきでしょうか? webkitdirectory
が HTMLInputElement
に指定されている場合、webkitEntries
は設定されません。ディレクトリ構造の再構築には files
コレクションと webkitRelativePath
プロパティを使用する必要があります。常に設定されるように修正すべきでしょうか?
6. HTML:ドラッグ&ドロップ
ドラッグ&ドロップ操作中、ファイルおよびディレクトリ項目はエントリに関連付けられます。各エントリは、そのルートディレクトリのメンバーであり、ドラッグデータストアに固有です。
さらに、各ディレクトリ項目は、ドラッグデータストアアイテムリスト内で、kindがFileであるアイテムとして表現されます。getAsFile()
でアクセスすると、長さ0のFile
が返されます。
注: ユーザーエージェントは、ドラッグ&ドロップ操作中に任意の階層データをファイルやディレクトリとして表現できます。例として、アルバムのメタデータとトラックのバイナリが別テーブルに格納されたリレーショナルデータベース内の音声データが、メディアプレーヤーアプリケーションからドラッグされた際にディレクトリやファイルとしてスクリプトへ公開されることが考えられます。
partial interface DataTransferItem {FileSystemEntry ?webkitGetAsEntry (); };
webkitGetAsEntry()
メソッドの手順:
-
storeを、thisの
DataTransfer
オブジェクトのドラッグデータストアとする。 -
storeのドラッグデータストアモードが読取/書込モードまたは読取専用モードでなければ、nullを返して手順を中止する。
-
itemを、storeのドラッグデータストアアイテムリスト内でthis が表すアイテムとする。
-
itemのkindがFileでなければ、nullを返して手順を中止する。
-
エントリを表す新しい
FileSystemEntry
オブジェクトを返す。
elem. addEventListener( 'dragover' , e=> { // ナビゲーション防止 e. preventDefault(); }); elem. addEventListener( 'drop' , e=> { // ナビゲーション防止 e. preventDefault(); // すべてのアイテムを処理 for ( const itemof e. dataTransfer. items) { // kindはファイル/ディレクトリエントリの場合 'file' if ( item. kind=== 'file' ) { const entry= item. webkitGetAsEntry(); handleEntry( entry); } } });
7. ファイルとディレクトリ
callback =
ErrorCallback undefined (DOMException );
err
ErrorCallback
関数は、エラーが非同期で返される可能性のある操作で使用されます。
-
file reading task source [FileAPI](
readEntries()
の成功・エラーコールバック)。Chromium実装ではTaskType::kFileReading
。 -
DOM操作タスクソース [HTML](Chromium実装では
TaskType::kMiscPlatformAPI
がTaskType::kDOMManipulation
と同じキューを使用)
7.1. FileSystemEntry
インターフェース
[Exposed =Window ]interface {
FileSystemEntry readonly attribute boolean isFile ;readonly attribute boolean isDirectory ;readonly attribute USVString name ;readonly attribute USVString fullPath ;readonly attribute FileSystem filesystem ;undefined getParent (optional FileSystemEntryCallback ,
successCallback optional ErrorCallback ); };
errorCallback
FileSystemEntry
には関連付けられたエントリがあります。
isFile
ゲッターの手順は、thisがファイルエントリならtrue、それ以外はfalseを返します。
isDirectory
ゲッターの手順は、thisがディレクトリエントリならtrue、それ以外はfalseを返します。
fullPath
ゲッターの手順は、thisのフルパスを返します。
filesystem
ゲッターの手順は、thisのファイルシステムを返します。
getParent(successCallback, errorCallback)
メソッドの手順:
-
並列で次の手順を実行:
-
itemが失敗の場合、タスクをキューに入れることで、コールバックを呼び出す。errorCallback(指定されていれば)に« 新しく生成された "
NotFoundError
"DOMException
»および"report
"を渡し、手順を中止。 -
entryを、itemの名前を名前、pathをフルパスとする新しいディレクトリエントリとする。
-
タスクをキューに入れることで、コールバックを呼び出す。successCallbackに«
FileSystemDirectoryEntry
オブジェクト(entryに関連付け) »および"report
"を渡す。
注: FileSystemEntry
作成後にファイルがディスク上で変更されていた場合、エラーになる可能性があります。
function handleEntry( entry) { console. log( 'name: ' + entry. name); console. log( 'path: ' + entry. fullPath); if ( entry. isFile) { console. log( '... ファイルです' ); } else if ( entry. isDirectory) { console. log( '... ディレクトリです' ); } }
getParent()
をPromiseで利用できるようにするヘルパー関数の例 [ECMA-262]:
function getParentAsPromise( entry) { return new Promise(( resolve, reject) => { entry. getParent( resolve, reject); }); }
7.2. FileSystemDirectoryEntry
インターフェース
[Exposed =Window ]interface :
FileSystemDirectoryEntry FileSystemEntry {FileSystemDirectoryReader createReader ();undefined getFile (optional USVString ?,
path optional FileSystemFlags = {},
options optional FileSystemEntryCallback ,
successCallback optional ErrorCallback );
errorCallback undefined getDirectory (optional USVString ?,
path optional FileSystemFlags = {},
options optional FileSystemEntryCallback ,
successCallback optional ErrorCallback ); };
errorCallback dictionary {
FileSystemFlags boolean =
create false ;boolean =
exclusive false ; };callback =
FileSystemEntryCallback undefined (FileSystemEntry );
entry
注: create
メンバーと
関連する挙動は、既存実装との互換性のために含まれていますが、このフラグが指定されても有用な挙動はありません。同様に、exclusive
メンバーは明示的に参照されていませんが、getterをもつオブジェクトが渡された場合スクリプトからbinding挙動は観測可能です。
FileSystemDirectoryEntry
の関連付けられたエントリはディレクトリエントリです。
createReader()
メソッドの手順:
-
新しく作成された
FileSystemDirectoryReader
オブジェクト(ディレクトリエントリに関連付け)を返す。
getFile(path, options, successCallback, errorCallback)
メソッドの手順:
-
並列で次の手順を実行:
-
pathが未定義またはnullなら空文字列にする。
-
pathが有効なパスでなければ、タスクをキューに入れることでerrorCallback(指定されていれば)に« 新しく生成された "
TypeMismatchError
"DOMException
»および"report
"を渡し、手順を中止。 -
optionsの
create
メンバーがtrueなら、タスクをキューに入れることでerrorCallback(指定されていれば)に« 新しく生成された "SecurityError
"DOMException
»および"report
"を渡し、手順を中止。 -
itemが失敗の場合、タスクをキューに入れることでerrorCallback(指定されていれば)に« 新しく生成された "
NotFoundError
"DOMException
»および"report
"を渡し、手順を中止。 -
itemがファイルでなければ、タスクをキューに入れることでerrorCallback(指定されていれば)に« 新しく生成された "
TypeMismatchError
"DOMException
»および"report
"を渡し、手順を中止。 -
タスクをキューに入れることでsuccessCallback(指定されていれば)に«
FileSystemFileEntry
オブジェクト(entryに関連付け) »および"report
"を渡す。
-
getDirectory(path, options, successCallback, errorCallback)
メソッドの手順:
-
並列で次の手順を実行:
-
pathが未定義またはnullなら空文字列にする。
-
pathが有効なパスでなければ、タスクをキューに入れることでerrorCallback(指定されていれば)に« 新しく生成された "
TypeMismatchError
"DOMException
»および"report
"を渡し、手順を中止。 -
optionsの
create
メンバーがtrueなら、タスクをキューに入れることでerrorCallback(指定されていれば)に« 新しく生成された "SecurityError
"DOMException
»および"report
"を渡し、手順を中止。 -
itemが失敗の場合、タスクをキューに入れることでerrorCallback(指定されていれば)に« 新しく生成された "
NotFoundError
"DOMException
»および"report
"を渡し、手順を中止。 -
itemがディレクトリでなければ、コールバックを呼び出すことでerrorCallback(指定されていれば)に« 新しく生成された "
TypeMismatchError
"DOMException
»および"report
"を渡し、手順を中止。 -
entryをitemの名前を名前、pathをフルパスとする新しいディレクトリエントリとする。
-
タスクをキューに入れることでsuccessCallback(指定されていれば)に«
FileSystemDirectoryEntry
オブジェクト(entryに関連付け) »および"report
"を渡す。
-
getFile()
および getDirectory()
をPromiseで利用できるようにするヘルパー関数の例 [ECMA-262]:
function getFileAsPromise( entry, path) { return new Promise(( resolve, reject) => { entry. getFile( path, {}, resolve, reject); }); } function getDirectoryAsPromise( entry, path) { return new Promise(( resolve, reject) => { entry. getDirectory( path, {}, resolve, reject); }); }
7.3. FileSystemDirectoryReader
インターフェース
[Exposed =Window ]interface {
FileSystemDirectoryReader undefined (
readEntries FileSystemEntriesCallback ,
successCallback optional ErrorCallback ); };
errorCallback callback =
FileSystemEntriesCallback undefined (sequence <FileSystemEntry >);
entries
FileSystemDirectoryReader
には、
関連付けられた エントリ(ディレクトリエントリ)、
関連付けられた ディレクトリ(初期値はnull)、
読み込みフラグ(初期値はfalse)、
完了フラグ(初期値はfalse)、
リーダーエラー(初期値はnull)があります。
readEntries(successCallback, errorCallback)
メソッドの手順:
-
thisの読み込みフラグがtrueなら、タスクをキューに入れることでerrorCallbackに« 新しく 生成された "
InvalidStateError
"DOMException
»および"report
"を渡し、手順を中止。 -
thisのリーダーエラーがnullでない場合、タスクをキューに入れることでerrorCallback(指定されていれば)に« リーダーエラー »および"
report
"を渡し、手順を中止。 -
thisの完了フラグがtrueなら、タスクをキューに入れることでsuccessCallbackに空のリストおよび"
report
"を渡し、手順を中止。 -
並列で次の手順を実行:
-
-
dirが失敗の場合:
-
タスクをキューに入れることで次の手順を実行:
-
errorを新しく生成された "
NotFoundError
"DOMException
とする。 -
コールバックを呼び出すことでerrorCallback(指定されていれば)に« error »および"
report
"を渡す。
-
-
手順を中止。
-
-
entriesを、thisのディレクトリから、この
FileSystemDirectoryReader
でまだ返されていない非ゼロ件のエントリ(もしあれば)を取得。 -
前の手順が失敗した場合(例:ディレクトリが削除された、または権限が拒否された場合):
-
タスクをキューに入れることで次の手順を実行:
-
errorを適切な
DOMException
とする。 -
コールバックを呼び出すことでerrorCallback(指定されていれば)に« error »および"
report
"を渡す。
-
-
手順を中止。
-
-
タスクをキューに入れることで次の手順を実行:
-
コールバックを呼び出すことでsuccessCallbackに« entries »および"
report
"を渡す。
-
注: 読み込みフラグを利用することで、上記の並列手順が同時に複数実行されるのを防いでいます。これにより並列キューの指定が不要となります。
const reader= dirEntry. createReader(); const doBatch= () => { // バッチを読む reader. readEntries( entries=> { // 完了? if ( entries. length=== 0 ) { return ; } // バッチを処理 entries. forEach( handleEntry); // 次のバッチを読む doBatch(); }, error=> console. warn( error)); }; // 読み取り開始 doBatch();
FileSystemDirectoryReader
をPromiseで利用できるようにするヘルパー関数の例 [ECMA-262]:
function getEntriesAsPromise( dirEntry) { return new Promise(( resolve, reject) => { const result= []; const reader= dirEntry. createReader(); const doBatch= () => { reader. readEntries( entries=> { if ( entries. length> 0 ) { entries. forEach( e=> result. push( e)); doBatch(); } else { resolve( result); } }, reject); }; doBatch(); }); }
FileSystemDirectoryReader
をAsyncIteratorで利用できるようにするヘルパー関数の例 [ECMA-262]:
async function * getEntriesAsAsyncIterator( dirEntry) { const reader= dirEntry. createReader(); const getNextBatch= () => new Promise(( resolve, reject) => { reader. readEntries( resolve, reject); }); let entries; do { entries= await getNextBatch(); for ( const entryof entries) { yield entry; } } while ( entries. length> 0 ); }
これによりfor-await-of
でディレクトリツリーを順序付き非同期でトラバースできます:
async function show( entry) { console. log( entry. fullPath); if ( entry. isDirectory) { for await ( const eof getEntriesAsAsyncIterator( entry)) { await show( e); } } }
7.4. FileSystemFileEntry
インターフェース
[Exposed =Window ]interface :
FileSystemFileEntry FileSystemEntry {undefined file (FileCallback ,
successCallback optional ErrorCallback ); };
errorCallback callback =
FileCallback undefined (File );
file
FileSystemFileEntry
の関連付けられたエントリはファイルエントリです。
file(successCallback, errorCallback)
メソッドの手順:
-
並列で次の手順を実行:
-
itemが失敗の場合、タスクをキューに入れることでerrorCallback(指定されていれば)に« 新しく生成された "
NotFoundError
"DOMException
»および"report
"を渡し、手順を中止。 -
itemがディレクトリの場合、タスクをキューに入れることでerrorCallback(指定されていれば)に« 新しく生成された "
TypeMismatchError
"DOMException
»および"report
"を渡し、手順を中止。 -
タスクをキューに入れることでsuccessCallbackに«
File
オブジェクト(itemを表す) »および"report
"を渡す。
FileReader
でドロップされたファイルの内容を読む例:
function readFileEntry( entry) { entry. file( file=> { const reader= new FileReader(); reader. readAsText( file); reader. onerror= error=> console. warn( error); reader. onload= () => { console. log( reader. result); }; }, error=> console. warn( error)); }
file()
をPromiseで利用できるようにするヘルパー関数の例 [ECMA-262]:
function fileAsPromise( entry) { return new Promise(( resolve, reject) => { entry. file( resolve, reject); }); }
7.5. FileSystem
インターフェース
[Exposed =Window ]interface {
FileSystem readonly attribute USVString name ;readonly attribute FileSystemDirectoryEntry root ; };
FileSystem
には関連付けられたファイルシステムがあります。
root
ゲッターの手順は、FileSystemDirectoryEntry
(thisのルートに関連付け)が返されます。
8. 謝辞
この仕様は、[file-system-api]におけるEric Uhrhane氏の成果に大きく基づいており、そこでFileSystemEntry
型が導入されました。
仕様作成ツールBikeshedを作成・管理してくださったTab Atkins, Jr.氏に感謝します。
そして Ali Alabbas氏、 Philip Jägenstedt氏、 Marijn Kruisselbrink氏、 Olli Pettay氏、 Kent Tamura氏 にご提案・レビュー・その他のフィードバックをいただき、感謝します。