deno/js/blob.ts

127 lines
3.9 KiB
TypeScript

// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import * as domTypes from "./dom_types";
import { containsOnlyASCII, hasOwnProperty } from "./util";
import { TextEncoder } from "./text_encoding";
export const bytesSymbol = Symbol("bytes");
function convertLineEndingsToNative(s: string): string {
// TODO(qti3e) Implement convertLineEndingsToNative.
// https://w3c.github.io/FileAPI/#convert-line-endings-to-native
return s;
}
function toUint8Arrays(
blobParts: domTypes.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));
// eslint-disable-next-line @typescript-eslint/no-use-before-define
} else if (element instanceof DenoBlob) {
ret.push(element[bytesSymbol]);
} else if (element instanceof Uint8Array) {
ret.push(element);
} else if (element instanceof Uint16Array) {
const uint8 = new Uint8Array(element.buffer);
ret.push(uint8);
} else if (element instanceof Uint32Array) {
const uint8 = new Uint8Array(element.buffer);
ret.push(uint8);
} 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);
} else {
ret.push(enc.encode(String(element)));
}
}
return ret;
}
function processBlobParts(
blobParts: domTypes.BlobPart[],
options: domTypes.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;
}
export class DenoBlob implements domTypes.Blob {
private readonly [bytesSymbol]: Uint8Array;
readonly size: number = 0;
readonly type: string = "";
/** A blob object represents a file-like object of immutable, raw data. */
constructor(
blobParts?: domTypes.BlobPart[],
options?: domTypes.BlobPropertyBag
) {
if (arguments.length === 0) {
this[bytesSymbol] = new Uint8Array();
return;
}
options = options || {};
// Set ending property's default value to "transparent".
if (!hasOwnProperty(options, "ending")) {
options.ending = "transparent";
}
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
});
}
}