summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorParsa Ghadimi <me@qti3e.com>2018-09-14 17:15:50 +0430
committerRyan Dahl <ry@tinyclouds.org>2018-09-14 10:04:10 -0700
commit7b7052e1abb0735ca443ffd133b014a19b7dab3d (patch)
treecee20d0143b9a0a7545b60ab329a17d1e17d9c11 /js
parentaaf70ca092fb9866bd50725f8f5b67be6f6879e3 (diff)
Implement Blob
Diffstat (limited to 'js')
-rw-r--r--js/blob.ts113
-rw-r--r--js/blob_test.ts35
-rw-r--r--js/fetch_types.d.ts3
-rw-r--r--js/globals.ts4
-rw-r--r--js/unit_tests.ts1
-rw-r--r--js/util.ts9
6 files changed, 165 insertions, 0 deletions
diff --git a/js/blob.ts b/js/blob.ts
new file mode 100644
index 000000000..128146ece
--- /dev/null
+++ b/js/blob.ts
@@ -0,0 +1,113 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+import { Blob, BlobPart, BlobPropertyBag } from "./fetch_types";
+import { containsOnlyASCII } from "./util";
+
+const bytesSymbol = Symbol("bytes");
+
+export class DenoBlob implements Blob {
+ private readonly [bytesSymbol]: Uint8Array;
+ readonly size: number = 0;
+ readonly type: string = "";
+
+ constructor(blobParts?: BlobPart[], options?: BlobPropertyBag) {
+ if (arguments.length === 0) {
+ this[bytesSymbol] = new Uint8Array();
+ return;
+ }
+
+ options = options || {};
+ // Set ending property's default value to "tranparent".
+ if (!options.hasOwnProperty("ending")) {
+ options.ending = "tranparent";
+ }
+
+ if (options.type && !containsOnlyASCII(options.type)) {
+ const errMsg = "The 'type' property must consist of ASCII characters.";
+ throw new SyntaxError(errMsg);
+ }
+
+ const bytes = processBlobParts(blobParts!, options);
+ // Normalize options.type.
+ let type = options.type ? options.type : "";
+ if (type.length) {
+ for (let i = 0; i < type.length; ++i) {
+ const char = type[i];
+ if (char < "\u0020" || char > "\u007E") {
+ type = "";
+ break;
+ }
+ }
+ type = type.toLowerCase();
+ }
+ // Set Blob object's properties.
+ this[bytesSymbol] = bytes;
+ this.size = bytes.byteLength;
+ this.type = type;
+ }
+
+ slice(start?: number, end?: number, contentType?: string): DenoBlob {
+ return new DenoBlob([this[bytesSymbol].slice(start, end)], {
+ type: contentType || this.type
+ });
+ }
+}
+
+function processBlobParts(
+ blobParts: BlobPart[],
+ options: BlobPropertyBag
+): Uint8Array {
+ const normalizeLineEndingsToNative = options.ending === "native";
+ // ArrayBuffer.transfer is not yet implemented in V8, so we just have to
+ // pre compute size of the array buffer and do some sort of static allocation
+ // instead of dynamic allocation.
+ const uint8Arrays = toUint8Arrays(blobParts, normalizeLineEndingsToNative);
+ const byteLength = uint8Arrays
+ .map(u8 => u8.byteLength)
+ .reduce((a, b) => a + b, 0);
+ const ab = new ArrayBuffer(byteLength);
+ const bytes = new Uint8Array(ab);
+
+ let courser = 0;
+ for (const u8 of uint8Arrays) {
+ bytes.set(u8, courser);
+ courser += u8.byteLength;
+ }
+
+ return bytes;
+}
+
+function toUint8Arrays(
+ blobParts: BlobPart[],
+ doNormalizeLineEndingsToNative: boolean
+): Uint8Array[] {
+ const ret: Uint8Array[] = [];
+ const enc = new TextEncoder();
+ for (const element of blobParts) {
+ if (typeof element === "string") {
+ let str = element;
+ if (doNormalizeLineEndingsToNative) {
+ str = convertLineEndingsToNative(element);
+ }
+ ret.push(enc.encode(str));
+ } else if (element instanceof DenoBlob) {
+ ret.push(element[bytesSymbol]);
+ } else if (element instanceof Uint8Array) {
+ ret.push(element);
+ } else if (ArrayBuffer.isView(element)) {
+ // Convert view to Uint8Array.
+ const uint8 = new Uint8Array(element.buffer);
+ ret.push(uint8);
+ } else if (element instanceof ArrayBuffer) {
+ // Create a new Uint8Array view for the given ArrayBuffer.
+ const uint8 = new Uint8Array(element);
+ ret.push(uint8);
+ }
+ }
+ return ret;
+}
+
+function convertLineEndingsToNative(s: string): string {
+ // TODO(qti3e) Implement convertLineEndingsToNative.
+ // https://w3c.github.io/FileAPI/#convert-line-endings-to-native
+ return s;
+}
diff --git a/js/blob_test.ts b/js/blob_test.ts
new file mode 100644
index 000000000..293d475dd
--- /dev/null
+++ b/js/blob_test.ts
@@ -0,0 +1,35 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+import { test, assert, assertEqual } from "./test_util.ts";
+
+test(async function blobString() {
+ const b1 = new Blob(["Hello World"]);
+ const str = "Test";
+ const b2 = new Blob([b1, str]);
+ assertEqual(b2.size, b1.size + str.length);
+});
+
+test(async function blobBuffer() {
+ const buffer = new ArrayBuffer(12);
+ const u8 = new Uint8Array(buffer);
+ const f1 = new Float32Array(buffer);
+ const b1 = new Blob([buffer, u8]);
+ assertEqual(b1.size, 2 * u8.length);
+ const b2 = new Blob([b1, f1]);
+ assertEqual(b2.size, 3 * u8.length);
+});
+
+test(async function blobSlice() {
+ const blob = new Blob(["Deno", "Foo"]);
+ const b1 = blob.slice(0, 3, "Text/HTML");
+ assert(b1 instanceof Blob);
+ assertEqual(b1.size, 3);
+ assertEqual(b1.type, "text/html");
+ const b2 = blob.slice(-1, 3);
+ assertEqual(b2.size, 0);
+ const b3 = blob.slice(100, 3);
+ assertEqual(b3.size, 0);
+ const b4 = blob.slice(0, 10);
+ assertEqual(b4.size, blob.size);
+});
+
+// TODO(qti3e) Test the stored data in a Blob after implementing FileReader API.
diff --git a/js/fetch_types.d.ts b/js/fetch_types.d.ts
index 9d4082c35..5f49a88d1 100644
--- a/js/fetch_types.d.ts
+++ b/js/fetch_types.d.ts
@@ -43,8 +43,11 @@ interface HTMLFormElement {
// TODO
}
+type EndingType = "tranparent" | "native";
+
interface BlobPropertyBag {
type?: string;
+ ending?: EndingType;
}
interface AbortSignalEventMap {
diff --git a/js/globals.ts b/js/globals.ts
index 3805f12f5..912af7dc4 100644
--- a/js/globals.ts
+++ b/js/globals.ts
@@ -7,6 +7,7 @@ import * as fetch_ from "./fetch";
import { libdeno } from "./libdeno";
import { globalEval } from "./global-eval";
import { DenoHeaders } from "./fetch";
+import { DenoBlob } from "./blob";
declare global {
interface Window {
@@ -26,6 +27,7 @@ declare global {
TextDecoder: typeof TextDecoder;
Headers: typeof Headers;
+ Blob: typeof Blob;
}
const clearTimeout: typeof timers.clearTimer;
@@ -42,6 +44,7 @@ declare global {
const TextEncoder: typeof textEncoding.TextEncoder;
const TextDecoder: typeof textEncoding.TextDecoder;
const Headers: typeof DenoHeaders;
+ const Blob: typeof DenoBlob;
// tslint:enable:variable-name
}
@@ -63,3 +66,4 @@ window.TextDecoder = textEncoding.TextDecoder;
window.fetch = fetch_.fetch;
window.Headers = DenoHeaders;
+window.Blob = DenoBlob;
diff --git a/js/unit_tests.ts b/js/unit_tests.ts
index be947cd67..e1385e011 100644
--- a/js/unit_tests.ts
+++ b/js/unit_tests.ts
@@ -11,3 +11,4 @@ import "./mkdir_test.ts";
import "./make_temp_dir_test.ts";
import "./stat_test.ts";
import "./rename_test.ts";
+import "./blob_test.ts";
diff --git a/js/util.ts b/js/util.ts
index f65477ee5..c4bada589 100644
--- a/js/util.ts
+++ b/js/util.ts
@@ -85,6 +85,7 @@ export function unreachable(): never {
throw new Error("Code not reachable");
}
+// @internal
export function hexdump(u8: Uint8Array): string {
return Array.prototype.map
.call(u8, (x: number) => {
@@ -92,3 +93,11 @@ export function hexdump(u8: Uint8Array): string {
})
.join(" ");
}
+
+// @internal
+export function containsOnlyASCII(str: string): boolean {
+ if (typeof str !== "string") {
+ return false;
+ }
+ return /^[\x00-\x7F]*$/.test(str);
+}