file-system-access
Version:
File System Access API implementation (ponyfill) with pluggable storage adapters via IndexedDB, Cache API, in-memory etc.
253 lines (229 loc) • 7.45 kB
JavaScript
import { ReadableStream } from '../lib/web-streams-ponyfill.js'
import tests from '../test/test.js'
import {
cleanupSandboxedFileSystem,
getDirectoryEntryCount,
assert
} from '../test/util.js'
import {
showDirectoryPicker,
showOpenFilePicker,
showSaveFilePicker,
getOriginPrivateDirectory
} from '../lib/es2018.js'
import '../lib/polyfillDataTransferItem.js'
if (!globalThis.WritableStream) {
// Tests use ReadableStream.pipeTo() which is only defined if WriteableStream is supported
globalThis.ReadableStream = ReadableStream
}
if (!Blob.prototype.text) {
Blob.prototype.text = function () {
return new Response(this).text()
}
Blob.prototype.arrayBuffer = function () {
return new Response(this).arrayBuffer()
}
Blob.prototype.stream = function () {
return new Response(this).body
}
}
let err
const tBody = document.querySelector('#table').tBodies[0]
/** @param {{desc: string}} n */
function t (n) {
const tr = tBody.insertRow()
const td = tr.insertCell()
td.innerText = n.desc
tr.insertCell()
tr.insertCell()
tr.insertCell()
tr.insertCell()
tr.insertCell()
}
tests.forEach(t)
function tt (n, html) {
const tr = manualTest.tBodies[0].insertRow()
tr.insertCell().innerText = n
tr.insertCell().appendChild(html())
}
function dt (files) {
const b = new ClipboardEvent('').clipboardData || new DataTransfer()
for (let i = 0, len = files.length; i < len; i++) b.items.add(files[i])
return b
}
try {
if (DataTransferItem.prototype.getAsFileSystemHandle.toString().includes('native'))
throw new Error(`Don't work with mocked data`)
const dataTransfer = dt([
new File(['content'], 'sample1.txt'),
new File(['abc'], 'sample2.txt')
])
// https://github.com/WICG/file-system-access/pull/192#issuecomment-847426013
for (const item of dataTransfer.items) {
item.getAsFileSystemHandle().then(handle => {
assert(handle.kind === 'file')
assert(handle[Symbol.toStringTag] === 'FileSystemFileHandle')
})
}
} catch (err) {}
// get some dummy gradient image
function img (format) {
const a = document.createElement('canvas')
const b = a.getContext('2d')
const c = b.createLinearGradient(0, 0, 1500, 1500)
a.width = a.height = 3000
c.addColorStop(0, 'red')
c.addColorStop(1, 'blue')
b.fillStyle = c
b.fillRect(0, 0, a.width, a.height)
return new Promise(rs => {
a.toBlob(rs, 'image/' + format, 1)
})
}
$types1.value = JSON.stringify([
{
description: 'Text Files',
accept: {
'text/plain': ['.txt', '.text'],
'text/html': ['.html', '.htm']
}
},
{
description: 'Images',
accept: {
'image/*': ['.png', '.gif', '.jpeg', '.jpg']
}
}
], null, 2)
$types2.value = JSON.stringify([
{
accept: { 'image/jpg': ['.jpg'] }
},
{
accept: { 'image/png': ['.png'] }
},
{
accept: { 'image/webp': ['.webp'] }
}
], null, 2)
form_showDirectoryPicker.onsubmit = evt => {
evt.preventDefault()
/** @type {Object<string, *>} */
const opts = Object.fromEntries([...new FormData(evt.target)])
opts._preferPolyfill = !!opts._preferPolyfill
showDirectoryPicker(opts).then(showFileStructure, console.error)
}
form_showOpenFilePicker.onsubmit = evt => {
evt.preventDefault()
/** @type {Object<string, *>} */
const opts = Object.fromEntries([...new FormData(evt.target)])
opts.types = JSON.parse(opts.types || '""')
opts._preferPolyfill = !!opts._preferPolyfill
showOpenFilePicker(opts).then(handles => {
console.log(handles)
alert(handles)
}, err => {
console.error(err)
alert(err)
})
}
form_showSaveFilePicker.onsubmit = async evt => {
evt.preventDefault()
/** @type {Object<string, *>} */
const opts = Object.fromEntries([...new FormData(evt.target)])
opts.types = JSON.parse(opts.types || '""')
opts._preferPolyfill = !!opts._preferPolyfill
const handle = await showSaveFilePicker(opts)
const format = handle.name.split('.').pop()
const image = await img(format)
const ws = await handle.createWritable()
await ws.write(image)
await ws.close()
}
async function init () {
const drivers = await Promise.allSettled([
getOriginPrivateDirectory(),
getOriginPrivateDirectory(import('../lib/adapters/sandbox.js')),
getOriginPrivateDirectory(import('../lib/adapters/memory.js')),
getOriginPrivateDirectory(import('../lib/adapters/indexeddb.js')),
getOriginPrivateDirectory(import('../lib/adapters/cache.js'))
])
let j = 0
for (const driver of drivers) {
j++
if (driver.status === 'rejected') {
console.error('Driver failed to load:' + driver.reason)
continue
}
const root = driver.value
await cleanupSandboxedFileSystem(root)
const total = performance.now()
for (var i = 0; i < tests.length; i++) {
const test = tests[i]
await cleanupSandboxedFileSystem(root)
const t = performance.now()
await test.fn(root).then(() => {
const time = (performance.now() - t).toFixed(3)
tBody.rows[i].cells[j].innerText = time + 'ms'
}, err => {
console.error(err)
tBody.rows[i].cells[j].innerText = '❌'
tBody.rows[i].cells[j].title = err.message
})
}
table.tFoot.rows[0].cells[j].innerText = (performance.now() - total).toFixed(3)
}
}
init().catch(console.error)
globalThis.ondragover = evt => evt.preventDefault()
globalThis.ondrop = async evt => {
evt.preventDefault()
for (const item of evt.dataTransfer.items) {
item.getAsFileSystemHandle().then(async handle => {
if (handle.kind === 'directory') {
showFileStructure(handle)
} else {
const file = await handle.getFile()
console.log(file)
alert(file)
}
})
}
}
/**
* @param {FileSystemDirectoryHandle} root
*/
async function showFileStructure (root) {
const result = []
/** @type {HTMLInputElement} */
const input = document.querySelector('[form=form_showOpenFilePicker][name="_preferPolyfill"]')
const readonly = input.checked
try {
readonly && assert(await getDirectoryEntryCount(root) > 0)
readonly && assert(await root.requestPermission({ writable: true }) === 'denied')
const dirs = [{ handle: root, path: "" }]
for (const { handle: dir, path } of dirs) {
const cwd = path + dir.name + '/'
for await (const [name, handle] of dir) {
// Everything should be read only
readonly && assert(await handle.requestPermission({ writable: true }) === 'denied')
readonly && assert(await handle.requestPermission({ readable: true }) === 'granted')
if (handle.kind === 'file') {
result.push(cwd + handle.name)
readonly && (err = await capture(handle.createWritable()))
readonly && assert(err.name === 'NotAllowedError')
} else {
result.push(cwd + handle.name + '/')
assert(handle.kind === 'directory')
dirs.push({ handle, path: cwd })
}
}
}
const json = JSON.stringify(result.sort(), null, 2)
console.log(json)
alert('assertion succeed\n' + json)
} catch (err) {
console.log(err)
alert('assertion failed - see console')
}
}