mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
Implement Blob
This commit is contained in:
parent
aaf70ca092
commit
7b7052e1ab
7 changed files with 166 additions and 0 deletions
1
BUILD.gn
1
BUILD.gn
|
@ -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
113
js/blob.ts
Normal 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
35
js/blob_test.ts
Normal 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
3
js/fetch_types.d.ts
vendored
|
@ -43,8 +43,11 @@ interface HTMLFormElement {
|
|||
// TODO
|
||||
}
|
||||
|
||||
type EndingType = "tranparent" | "native";
|
||||
|
||||
interface BlobPropertyBag {
|
||||
type?: string;
|
||||
ending?: EndingType;
|
||||
}
|
||||
|
||||
interface AbortSignalEventMap {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue