diff options
Diffstat (limited to 'op_crates/web/21_filereader.js')
-rw-r--r-- | op_crates/web/21_filereader.js | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/op_crates/web/21_filereader.js b/op_crates/web/21_filereader.js new file mode 100644 index 000000000..8a5262a7e --- /dev/null +++ b/op_crates/web/21_filereader.js @@ -0,0 +1,248 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +((window) => { + const base64 = window.__bootstrap.base64; + + async function readOperation(fr, blob, readtype) { + // Implementation from https://w3c.github.io/FileAPI/ notes + // And body of deno blob.ts readBytes + + fr.aborting = false; + + // 1. If fr’s state is "loading", throw an InvalidStateError DOMException. + if (fr.readyState === FileReader.LOADING) { + throw new DOMException( + "Invalid FileReader state.", + "InvalidStateError", + ); + } + // 2. Set fr’s state to "loading". + fr.readyState = FileReader.LOADING; + // 3. Set fr’s result to null. + fr.result = null; + // 4. Set fr’s error to null. + fr.error = null; + + // 5. Let stream be the result of calling get stream on blob. + const stream /*: ReadableStream<ArrayBufferView>*/ = blob.stream(); + + // 6. Let reader be the result of getting a reader from stream. + const reader = stream.getReader(); + + // 7. Let bytes be an empty byte sequence. + //let bytes = new Uint8Array(); + const chunks /*: Uint8Array[]*/ = []; + + // 8. Let chunkPromise be the result of reading a chunk from stream with reader. + let chunkPromise = reader.read(); + + // 9. Let isFirstChunk be true. + let isFirstChunk = true; + + // 10 in parallel while true + while (!fr.aborting) { + // 1. Wait for chunkPromise to be fulfilled or rejected. + try { + const chunk = await chunkPromise; + + // 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr. + if (isFirstChunk) { + queueMicrotask(() => { + // fire a progress event for loadstart + const ev = new ProgressEvent("loadstart", {}); + fr.dispatchEvent(ev); + if (fr.onloadstart !== null) { + fr.onloadstart(ev); + } + }); + } + // 3. Set isFirstChunk to false. + isFirstChunk = false; + + // 4. If chunkPromise is fulfilled with an object whose done property is false + // and whose value property is a Uint8Array object, run these steps: + if (!chunk.done && chunk.value instanceof Uint8Array) { + chunks.push(chunk.value); + + // TODO: (only) If roughly 50ms have passed since last progress + { + const size = chunks.reduce((p, i) => p + i.byteLength, 0); + const ev = new ProgressEvent("progress", { + loaded: size, + }); + fr.dispatchEvent(ev); + if (fr.onprogress !== null) { + fr.onprogress(ev); + } + } + + chunkPromise = reader.read(); + } // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm: + else if (chunk.done === true) { + queueMicrotask(() => { + if (fr.aborting) { + return; + } + + // 1. Set fr’s state to "done". + fr.readyState = FileReader.DONE; + // 2. Let result be the result of package data given bytes, type, blob’s type, and encodingName. + const size = chunks.reduce((p, i) => p + i.byteLength, 0); + const bytes = new Uint8Array(size); + let offs = 0; + for (const chunk of chunks) { + bytes.set(chunk, offs); + offs += chunk.byteLength; + } + switch (readtype.kind) { + case "ArrayBuffer": { + fr.result = bytes.buffer; + break; + } + case "Text": { + const decoder = new TextDecoder(readtype.encoding); + fr.result = decoder.decode(bytes.buffer); + break; + } + case "DataUrl": { + fr.result = "data:application/octet-stream;base64," + + base64.fromByteArray(bytes); + break; + } + } + // 4.2 Fire a progress event called load at the fr. + { + const ev = new ProgressEvent("load", { + lengthComputable: true, + loaded: size, + total: size, + }); + fr.dispatchEvent(ev); + if (fr.onload !== null) { + fr.onload(ev); + } + } + + // 5. If fr’s state is not "loading", fire a progress event called loadend at the fr. + //Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired. + if (fr.readyState !== FileReader.LOADING) { + const ev = new ProgressEvent("loadend", { + lengthComputable: true, + loaded: size, + total: size, + }); + fr.dispatchEvent(ev); + if (fr.onloadend !== null) { + fr.onloadend(ev); + } + } + }); + + break; + } + } catch (err) { + if (fr.aborting) { + break; + } + + // chunkPromise rejected + fr.readyState = FileReader.DONE; + fr.error = err; + + { + const ev = new ProgressEvent("error", {}); + fr.dispatchEvent(ev); + if (fr.onerror !== null) { + fr.onerror(ev); + } + } + + //If fr’s state is not "loading", fire a progress event called loadend at fr. + //Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired. + if (fr.readyState !== FileReader.LOADING) { + const ev = new ProgressEvent("loadend", {}); + fr.dispatchEvent(ev); + if (fr.onloadend !== null) { + fr.onloadend(ev); + } + } + + break; + } + } + } + + class FileReader extends EventTarget { + error = null; + onabort = null; + onerror = null; + onload = null; + onloadend = null; + onloadstart = null; + onprogress = null; + + readyState = FileReader.EMPTY; + result = null; + aborting = false; + + constructor() { + super(); + } + + abort() { + // If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm. + if ( + this.readyState === FileReader.EMPTY || + this.readyState === FileReader.DONE + ) { + this.result = null; + return; + } + // If context object's state is "loading" set context object's state to "done" and set context object's result to null. + if (this.readyState === FileReader.LOADING) { + this.readyState = FileReader.DONE; + this.result = null; + } + // If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue. + // Terminate the algorithm for the read method being processed. + this.aborting = true; + + // Fire a progress event called abort at the context object. + const ev = new ProgressEvent("abort", {}); + this.dispatchEvent(ev); + if (this.onabort !== null) { + this.onabort(ev); + } + + // If context object's state is not "loading", fire a progress event called loadend at the context object. + if (this.readyState !== FileReader.LOADING) { + const ev = new ProgressEvent("loadend", {}); + this.dispatchEvent(ev); + if (this.onloadend !== null) { + this.onloadend(ev); + } + } + } + readAsArrayBuffer(blob) { + readOperation(this, blob, { kind: "ArrayBuffer" }); + } + readAsBinaryString(blob) { + // alias for readAsArrayBuffer + readOperation(this, blob, { kind: "ArrayBuffer" }); + } + readAsDataURL(blob) { + readOperation(this, blob, { kind: "DataUrl" }); + } + readAsText(blob, encoding) { + readOperation(this, blob, { kind: "Text", encoding }); + } + } + + FileReader.EMPTY = 0; + FileReader.LOADING = 1; + FileReader.DONE = 2; + + window.__bootstrap.fileReader = { + FileReader, + }; +})(this); |