diff --git a/index.bs b/index.bs index 85afe86..1608cce 100644 --- a/index.bs +++ b/index.bs @@ -61,54 +61,6 @@ different storage mechanism with a different API for such files. The entry point A file system entry is either a [=file entry=] or a [=directory entry=]. -Each [=/file system entry=] has an associated -query access -algorithm, which takes "`read`" or "`readwrite`" mode and -returns a [=/file system access result=]. -Unless specified otherwise it returns a [=/file system access result=] with a -[=file system access result/permission state=] of "{{PermissionState/denied}}" -and with an [=file system access result/error name=] of the empty string. - -Each [=/file system entry=] has an associated -request access -algorithm, which takes "`read`" or "`readwrite`" mode and -returns a [=/file system access result=]. -Unless specified otherwise it returns a [=/file system access result=] with a -[=file system access result/permission state=] of "{{PermissionState/denied}}" -and with an [=file system access result/error name=] of the empty string. - -A file system access result is a [=struct=] encapsulating the -result of [=file system entry/query access|querying=] or -[=file system entry/request access|requesting=] access to the file system. -It has the following [=struct/items=]: - -: permission state -:: A {{PermissionState}} -: error name -:: A [=string=] which must be the empty string if - [=file system access result/permission state=] is - "{{PermissionState/granted}}"; otherwise an - [=DOMException/name=] listed in the `DOMException` names table. - It is expected that in most cases when - [=file system access result/permission state=] is not - "{{PermissionState/granted}}", this should be "{{NotAllowedError}}". - -

Dependent specifications may consider this API a -[=powerful feature=]. However, unlike other [=powerful features=] whose -[=permission request algorithm=] may throw, [=/file system entry=]'s -[=file system entry/query access=] and [=file system entry/request access=] -algorithms must run [=in parallel=] on the [=file system queue=] and are -therefore not allowed to throw. Instead, the caller is expected to -[=queue a storage task=] to [=/reject=], as appropriate, -should these algorithms return an [=file system access result/error name=] -other than the empty string. - -Note: Implementations that only implement this specification and not dependent -specifications do not need to bother implementing [=/file system entry=]'s -[=file system entry/query access=] and [=file system entry/request access=]. - -Issue(101): Make access check algorithms associated with a FileSystemHandle. - Each [=/file system entry=] has an associated name (a [=string=]). A valid file name is a [=string=] that is not an empty string, is not equal to "." or "..", @@ -266,6 +218,20 @@ a [=/file system locator=] |b| if |a|'s [=file system locator/root=] is |b|'s [=file system locator/root=], and |a|'s [=file system locator/path=] is [=the same path as=] |b|'s [=file system locator/path=]. +A [=/file system locator=] +is in a bucket file system +if the first [=list/item=] of its +[=file system locator/path=] is the empty string. + +Note: This is a bit magical, but it works since only the root directory of a +[=/bucket file system=] can have a [=file system locator/path=] which +[=list/contains=] an empty string. See {{StorageManager/getDirectory()}}. +All other [=list/item=]s of a [=file system locator/path=] will be a +[=valid file name=]. + +Issue(109): Consider improving this situation by giving each locator a +[=storage bucket=]. +

The locate an entry algorithm given a [=/file system locator=] |locator| runs an [=implementation-defined=] series of steps adhering to @@ -318,6 +284,62 @@ with the website process. The [=/file system path=] might contain components which are not known to the website unless the [=/file system locator=] is later [=file system locator/resolved=] relative to a parent [=directory locator=]. +
+QueryFileSystemAccess(|locator|, |mode|) +runs these steps, unless specified otherwise: + +1. Assert: |locator| is a [=/file system locator=]. +1. Assert: |mode| is "`read`" or "`readwrite`". +1. If |locator| [=file system locator/is in a bucket file system=] + return a [=/file system access result=] with + a [=file system access result/permission state=] + of "{{PermissionState/granted}}" and with + an [=file system access result/error name=] of the empty string. +1. Otherwise return a [=/file system access result=] with + a [=file system access result/permission state=] + of "{{PermissionState/denied}}" and with + an [=file system access result/error name=] of the empty string. + +Note: These steps have to be run on the [=file system queue=]. + +
+ +RequestFileSystemAccess(|locator|, |mode|) +returns [$QueryFileSystemAccess$](|locator|, |mode|), +unless specified otherwise. + +Note: Dependent specifications may override +[$QueryFileSystemAccess$] and [$RequestFileSystemAccess$]. + +A file system access result is a [=struct=] encapsulating the +result of running [$QueryFileSystemAccess$] or [$RequestFileSystemAccess$]. +It has the following [=struct/items=]: + +: permission state +:: A {{PermissionState}} +: error name +:: A [=string=] which must be the empty string if + [=file system access result/permission state=] is + "{{PermissionState/granted}}"; otherwise an + [=DOMException/name=] listed in the `DOMException` names table. + It is expected that in most cases when + [=file system access result/permission state=] is not + "{{PermissionState/granted}}", this should be "{{NotAllowedError}}". + +

Dependent specifications may consider this API a +[=powerful feature=]. However, unlike other [=powerful features=] whose +[=permission request algorithm=] may throw, +[$QueryFileSystemAccess$] and [$RequestFileSystemAccess$] +each must run [=in parallel=] on the [=file system queue=] and are +therefore not allowed to throw. Instead, the caller is expected to +[=queue a storage task=] to [=/reject=], as appropriate, +should these algorithms return an [=file system access result/error name=] +other than the empty string. + +

To check whether a website can access a +{{FileSystemHandle}} |handle| with |mode|, run +[$QueryFileSystemAccess$](|handle|'s [=FileSystemHandle/permission root locator=], |mode|). + ## The {{FileSystemHandle}} interface ## {#api-filesystemhandle}

@@ -335,25 +357,19 @@ interface FileSystemHandle { }; -A {{FileSystemHandle}} object is associated with a locator (a -[=/file system locator=]). +A {{FileSystemHandle}} object is associated with +a locator (a [=/file system locator=]) +that describes the location in the file system this handle corresponds to and +a permission root locator (a [=/file system locator=]) +used for querying and requesting permission to use this handle. Note: Multiple {{FileSystemHandle}} objects can have [=the same locator as|the same=] [=/file system locator=]. A {{FileSystemHandle}} is in a bucket file system -if the first [=list/item=] of its [=FileSystemHandle/locator=]'s -[=file system locator/path=] is the empty string. - -Note: This is a bit magical, but it works since only the root directory of a -[=/bucket file system=] can have a [=file system locator/path=] which -[=list/contains=] an empty string. See {{StorageManager/getDirectory()}}. -All other [=list/item=]s of a [=file system locator/path=] will be a -[=valid file name=]. - -Issue(109): Consider improving this situation by giving each locator a -[=storage bucket=]. +if its [=FileSystemHandle/locator=] +[=file system locator/is in a bucket file system=].
{{FileSystemHandle}} objects are [=serializable objects=]. @@ -362,6 +378,8 @@ Their [=serialization steps=], given |value|, |serialized| and forSt 1. Set |serialized|.\[[Origin]] to |value|'s [=relevant settings object=]'s [=environment settings object/origin=]. 1. Set |serialized|.\[[Locator]] to |value|'s [=FileSystemHandle/locator=]. +1. Set |serialized|.\[[PermissionRootLocator]] to |value|'s + [=FileSystemHandle/permission root locator=].
@@ -372,6 +390,7 @@ Their [=deserialization steps=], given |serialized| and |value| are: |value|'s [=relevant settings object=]'s [=environment settings object/origin=], then [=throw=] a "{{DataCloneError}}" {{DOMException}}. 1. Set |value|'s [=FileSystemHandle/locator=] to |serialized|.\[[Locator]]. +1. Set |value|'s [=FileSystemHandle/permission root locator=] to |serialized|.\[[PermissionRootLocator]].
@@ -438,7 +457,9 @@ Note: A {{FileSystemFileHandle}}'s associated [=FileSystemHandle/locator=]'s
To create a child `FileSystemFileHandle` -given a [=directory locator=] |parentLocator| and a string |name| in a [=/Realm=] |realm|: +given a [=directory locator=] |parentLocator|, a string |name|, and +a [=/file system locator=] |parentPermissionRootLocator| +in a [=/Realm=] |realm|: 1. Let |handle| be a [=new=] {{FileSystemFileHandle}} in |realm|. 1. Let |childType| be "{{FileSystemHandleKind/file}}". @@ -449,6 +470,14 @@ given a [=directory locator=] |parentLocator| and a string |name| in a [=/Realm= [=file system locator/kind=] is |childType|, [=file system locator/root=] is |childRoot|, and [=file system locator/path=] is |childPath|. +1. Set |handle|'s [=FileSystemHandle/permission root locator=] to + |parentPermissionRootLocator|. + + Note: This ensures that permissions are inherited from parent to child. + Querying access or requesting access to |handle| will + query access or request access to its parent - or its parent's parent, + and so forth - as appropriate. + 1. Return |handle|.
@@ -460,10 +489,12 @@ given a [=/file system root=] |root| and a [=/file system path=] |path| in a [=/Realm=] |realm|: 1. Let |handle| be a [=new=] {{FileSystemFileHandle}} in |realm|. -1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose +1. Let |locator| be a [=/file system locator=] whose [=file system locator/kind=] is "{{FileSystemHandleKind/file}}", [=file system locator/root=] is |root|, and [=file system locator/path=] is |path|. +1. Set |handle|'s [=FileSystemHandle/locator=] to |locator|. +1. Set |handle|'s [=FileSystemHandle/permission root locator=] to |locator|. 1. Return |handle|. @@ -486,11 +517,13 @@ The getFile() method steps are: 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. +1. Let |permissionRootLocator| be + [=this=]'s [=FileSystemHandle/permission root locator=]. 1. Let |global| be [=this=]'s [=relevant global object=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. Let |accessResult| be the result of running + [$QueryFileSystemAccess$](|permissionRootLocator|, "`read`"). 1. Let |entry| be the result of [=locating an entry=] given |locator|. - 1. Let |accessResult| be the result of running |entry|'s - [=file system entry/query access=] given "`read`". 1. [=Queue a storage task=] with |global| to run these steps: 1. If |accessResult|'s [=file system access result/permission state=] @@ -558,18 +591,20 @@ The createWritable(|options|) method 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. +1. Let |permissionRootLocator| be + [=this=]'s [=FileSystemHandle/permission root locator=]. 1. Let |realm| be [=this=]'s [=relevant Realm=]. 1. Let |global| be [=this=]'s [=relevant global object=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: - 1. Let |entry| be the result of [=locating an entry=] given |locator|. - 1. Let |accessResult| be the result of running |entry|'s - [=file system entry/request access=] given "`readwrite`". + 1. Let |accessResult| be the result of running + [$RequestFileSystemAccess$](|permissionRootLocator|, "`readwrite`"). 1. If |accessResult|'s [=file system access result/permission state=] is not "{{PermissionState/granted}}", [=queue a storage task=] with |global| to [=/reject=] |result| with a {{DOMException}} of |accessResult|'s [=file system access result/error name=] and abort these steps. + 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. @@ -582,7 +617,7 @@ The createWritable(|options|) method "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` - for |entry| in |realm|. + given |entry| and |permissionRootLocator| in |realm|. 1. If |options|["{{FileSystemCreateWritableOptions/keepExistingData}}"] is true: 1. Set |stream|'s [=[[buffer]]=] to a copy of |entry|'s @@ -620,15 +655,16 @@ The createSyncAccessHandle() method s 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. +1. Let |permissionRootLocator| be + [=this=]'s [=FileSystemHandle/permission root locator=]. 1. Let |realm| be [=this=]'s [=relevant Realm=]. 1. Let |global| be [=this=]'s [=relevant global object=]. 1. Let |isInABucketFileSystem| be true if [=this=] [=FileSystemHandle/is in a bucket file system=]; otherwise false. 1. [=Enqueue the following steps=] to the [=file system queue=]: - 1. Let |entry| be the result of [=locating an entry=] given |locator|. - 1. Let |accessResult| be the result of running |entry|'s - [=file system entry/request access=] given "`readwrite`". + 1. Let |accessResult| be the result of running + [$RequestFileSystemAccess$](|permissionRootLocator|, "`readwrite`"). 1. If |accessResult|'s [=file system access result/permission state=] is not "{{PermissionState/granted}}", [=queue a storage task=] with |global| to [=/reject=] |result| with a {{DOMException}} of @@ -640,6 +676,7 @@ The createSyncAccessHandle() method s [=/reject=] |result| with an "{{InvalidStateError}}" {{DOMException}} and abort these steps. + 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. @@ -693,7 +730,9 @@ Note: A {{FileSystemDirectoryHandle}}'s associated [=FileSystemHandle/locator=]'
To create a child `FileSystemDirectoryHandle` -given a [=directory locator=] |parentLocator| and a string |name| in a [=/Realm=] |realm|: +given a [=directory locator=] |parentLocator|, a string |name|, and +a [=/file system locator=] |parentPermissionRootLocator| +in a [=/Realm=] |realm|: 1. Let |handle| be a [=new=] {{FileSystemDirectoryHandle}} in |realm|. 1. Let |childType| be "{{FileSystemHandleKind/directory}}". @@ -704,6 +743,14 @@ given a [=directory locator=] |parentLocator| and a string |name| in a [=/Realm= [=file system locator/kind=] is |childType|, [=file system locator/root=] is |childRoot|, and [=file system locator/path=] is |childPath|. +1. Set |handle|'s [=FileSystemHandle/permission root locator=] to + |parentPermissionRootLocator|. + + Note: This ensures that permissions are inherited from parent to child. + Querying access or requesting access to |handle| will + query access or request access to its parent - or its parent's parent, + and so forth - as appropriate. + 1. Return |handle|.
@@ -715,10 +762,12 @@ given a [=/file system root=] |root| and a [=/file system path=] |path| in a [=/Realm=] |realm|: 1. Let |handle| be a [=new=] {{FileSystemDirectoryHandle}} in |realm|. -1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose +1. Let |locator| be a [=/file system locator=] whose [=file system locator/kind=] is "{{FileSystemHandleKind/directory}}", [=file system locator/root=] is |root|, and [=file system locator/path=] is |path|. +1. Set |handle|'s [=FileSystemHandle/locator=] to |locator|. +1. Set |handle|'s [=FileSystemHandle/permission root locator=] to |locator|. 1. Return |handle|. @@ -756,18 +805,20 @@ To [=get the next iteration result=] for a {{FileSystemDirectoryHandle}} |handle and its async iterator |iterator|: 1. Let |promise| be [=a new promise=]. +1. Let |permissionRootLocator| be |handle|'s + [=FileSystemHandle/permission root locator=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. Let |accessResult| be the result of running + [$QueryFileSystemAccess$](|permissionRootLocator|, "`read`"). 1. Let |directory| be the result of [=locating an entry=] given |handle|'s [=FileSystemHandle/locator=]. - 1. Let |accessResult| be the result of running |directory|'s - [=file system entry/query access=] given "`read`". 1. [=Queue a storage task=] with |handle|'s [=relevant global object=] to run these steps: 1. If |accessResult|'s [=file system access result/permission state=] is not "{{PermissionState/granted}}", [=/reject=] |promise| with a {{DOMException}} of |accessResult|'s - [=file system access result/error name=] and abort these steps.: + [=file system access result/error name=] and abort these steps. 1. If |directory| is `null`, [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. @@ -792,13 +843,17 @@ and its async iterator |iterator|: 1. If |child| is a [=file entry=]: 1. Let |result| be the result of creating a child `FileSystemFileHandle` with - |handle|'s [=FileSystemHandle/locator=] and - |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=]. + |handle|'s [=FileSystemHandle/locator=], + |child|'s [=file system entry/name=], and + |permissionRootLocator| + in |handle|'s [=relevant Realm=]. 1. Otherwise: 1. Let |result| be the result of creating a child `FileSystemDirectoryHandle` with - |handle|'s [=FileSystemHandle/locator=] and - |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=]. + |handle|'s [=FileSystemHandle/locator=], + |child|'s [=file system entry/name=], and + |permissionRootLocator| + in |handle|'s [=relevant Realm=]. 1. [=/Resolve=] |promise| with (|child|'s [=file system entry/name=], |result|). @@ -835,19 +890,22 @@ The getFileHandle(|name|, |options|)getFileHandle(|name|, |options|)creating a child `FileSystemFileHandle` with |locator| and - |child|'s [=file system entry/name=] in |realm| and + creating a child `FileSystemFileHandle` with |locator|, + |child|'s [=file system entry/name=], and + |permissionRootLocator| in |realm| and abort these steps. 1. If |options|["{{FileSystemGetFileOptions/create}}"] is false: 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. - 1. Let |child| be a new [=file entry=] whose [=query access=] and - [=request access=] algorithms are those of |entry|. + 1. Let |child| be a new [=file entry=]. 1. Set |child|'s [=file system entry/name=] to |name|. 1. Set |child|'s [=binary data=] to an empty [=byte sequence=]. 1. Set |child|'s [=modification timestamp=] to the current time. @@ -882,8 +940,9 @@ The getFileHandle(|name|, |options|)creating a child `FileSystemFileHandle` with |locator| and - |child|'s [=file system entry/name=] in |realm|. + creating a child `FileSystemFileHandle` with |locator|, + |child|'s [=file system entry/name=], and + |permissionRootLocator| in |realm|. 1. Return |result|. @@ -917,19 +976,22 @@ The getDirectoryHandle(|name|, |option 1. Let |result| be [=a new promise=]. 1. Let |realm| be [=this=]'s [=relevant Realm=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. +1. Let |permissionRootLocator| be + [=this=]'s [=FileSystemHandle/permission root locator=]. 1. Let |global| be [=this=]'s [=relevant global object=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: 1. If |name| is not a [=valid file name=], [=queue a storage task=] with |global| to [=/reject=] |result| with a {{TypeError}} and abort these steps. - 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. If |options|["{{FileSystemGetDirectoryOptions/create}}"] is true: - 1. Let |accessResult| be the result of running |entry|'s - [=file system entry/request access=] given "`readwrite`". + 1. Let |accessResult| be the result of running + [$RequestFileSystemAccess$](|permissionRootLocator|, "`readwrite`"). 1. Otherwise: - 1. Let |accessResult| be the result of running |entry|'s - [=file system entry/query access=] given "`read`". + 1. Let |accessResult| be the result of running + [$QueryFileSystemAccess$](|permissionRootLocator|, "`read`"). + + 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. [=Queue a storage task=] with |global| to run these steps: 1. If |accessResult|'s [=file system access result/permission state=] @@ -948,13 +1010,13 @@ The getDirectoryHandle(|name|, |option "{{TypeMismatchError}}" {{DOMException}} and abort these steps. 1. [=/Resolve=] |result| with the result of creating a child `FileSystemDirectoryHandle` with - |locator| and |child|'s [=file system entry/name=] in |realm| and + |locator|, |child|'s [=file system entry/name=], and + |permissionRootLocator| in |realm| and abort these steps. 1. If |options|["{{FileSystemGetFileOptions/create}}"] is false: 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. - 1. Let |child| be a new [=directory entry=] whose [=query access=] and - [=request access=] algorithms are those of |entry|. + 1. Let |child| be a new [=directory entry=]. 1. Set |child|'s [=file system entry/name=] to |name|. 1. Set |child|'s [=directory entry/children=] to an empty [=/set=]. 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=]. @@ -964,7 +1026,8 @@ The getDirectoryHandle(|name|, |option Issue(11): Better specify what possible exceptions this could throw. 1. [=/Resolve=] |result| with the result of creating a child `FileSystemDirectoryHandle` with - |locator| and |child|'s [=file system entry/name=] in |realm|. + |locator|, |child|'s [=file system entry/name=], and + |permissionRootLocator| in |realm|. 1. Return |result|. @@ -994,15 +1057,17 @@ The removeEntry(|name|, |options|) To create a new `FileSystemWritableFileStream` -given a [=file entry=] |file| in a [=/Realm=] |realm|: +given a [=file entry=] |file| and +a [=/file system locator=] |permissionRootLocator| +in a [=/Realm=] |realm|: 1. Let |stream| be a [=new=] {{FileSystemWritableFileStream}} in |realm|. 1. Set |stream|'s [=FileSystemWritableFileStream/[[file]]=] to |file|. 1. Let |writeAlgorithm| be an algorithm which takes a |chunk| argument - and returns the result of running the [=write a chunk=] algorithm with |stream| and |chunk|. + and returns the result of running the [=write a chunk=] algorithm with + |permissionRootLocator|, |stream|, and |chunk|. 1. Let |closeAlgorithm| be these steps: 1. Let |closeResult| be [=a new promise=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: - 1. Let |accessResult| be the result of running |file|'s - [=file system entry/query access=] given "`readwrite`". + 1. Let |accessResult| be the result of running + [$QueryFileSystemAccess$](|permissionRootLocator|, "`readwrite`"). 1. [=Queue a storage task=] with |file|'s [=relevant global object=] to run these steps: @@ -1203,8 +1271,10 @@ given a [=file entry=] |file| in a [=/Realm=] |realm|:
-The write a chunk algorithm, -given a {{FileSystemWritableFileStream}} |stream| and |chunk|, +The write a chunk algorithm, given +a [=/file system locator=] |permissionRootLocator|, +a {{FileSystemWritableFileStream}} |stream|, +and a |chunk|, runs these steps: 1. Let |input| be the result of [=converted to an IDL value|converting=] |chunk| to a {{FileSystemWriteChunkType}}. @@ -1212,8 +1282,7 @@ runs these steps: 1. Let |p| be [=a new promise=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: 1. Let |accessResult| be the result of running - |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s - [=file system entry/query access=] given "`readwrite`". + [$QueryFileSystemAccess$](|permissionRootLocator|, "`readwrite`"). 1. [=Queue a storage task=] with |stream|'s [=relevant global object=] to run these steps: @@ -1710,12 +1779,7 @@ The getDirectory() method steps are: return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}}. 1. If |map|["root"] does not [=map/exist=]: - 1. Let |dir| be a new [=directory entry=] whose [=query access=] and - [=request access=] algorithms always return - a [=/file system access result=] - with a [=file system access result/permission state=] - of "{{PermissionState/granted}}" and - with an [=file system access result/error name=] of the empty string. + 1. Let |dir| be a new [=directory entry=]. 1. Set |dir|'s [=file system entry/name=] to the empty string. 1. Set |dir|'s [=directory entry/children=] to an empty [=/set=]. 1. Set |map|["root"] to |dir|.