1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-22 15:06:54 -05:00

Implement Blob

This commit is contained in:
Parsa Ghadimi 2018-09-14 17:15:50 +04:30 committed by Ryan Dahl
parent aaf70ca092
commit 7b7052e1ab
7 changed files with 166 additions and 0 deletions

View file

@ -54,6 +54,7 @@ main_extern = [
ts_sources = [
"js/assets.ts",
"js/blob.ts",
"js/compiler.ts",
"js/console.ts",
"js/deno.ts",

113
js/blob.ts Normal file
View file

@ -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;
}

35
js/blob_test.ts Normal file
View file

@ -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.

3
js/fetch_types.d.ts vendored
View file

@ -43,8 +43,11 @@ interface HTMLFormElement {
// TODO
}
type EndingType = "tranparent" | "native";
interface BlobPropertyBag {
type?: string;
ending?: EndingType;
}
interface AbortSignalEventMap {

View file

@ -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;

View file

@ -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";

View file

@ -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);
}