Implement Blob url support for worker (#2729)

This commit is contained in:
Kevin (Kun) "Kassimo" Qian 2019-08-06 06:22:11 -07:00 committed by Ryan Dahl
parent 11c850af42
commit ccee2f01ba
8 changed files with 118 additions and 24 deletions

View file

@ -205,6 +205,8 @@ table FormatErrorRes {
table CreateWorker {
specifier: string;
include_deno_namespace: bool;
has_source_code: bool;
source_code: string;
}
table CreateWorkerRes {

View file

@ -2075,6 +2075,8 @@ fn op_create_worker(
// has included namespace (to avoid escalation).
let include_deno_namespace =
inner.include_deno_namespace() && state.include_deno_namespace;
let has_source_code = inner.has_source_code();
let source_code = inner.source_code().unwrap();
let parent_state = state.clone();
@ -2094,29 +2096,34 @@ fn op_create_worker(
worker.execute(&deno_main_call).unwrap();
worker.execute("workerMain()").unwrap();
let exec_cb = move |worker: Worker| {
let mut workers_tl = parent_state.workers.lock().unwrap();
workers_tl.insert(rid, worker.shared());
let builder = &mut FlatBufferBuilder::new();
let msg_inner =
msg::CreateWorkerRes::create(builder, &msg::CreateWorkerResArgs { rid });
serialize_response(
cmd_id,
builder,
msg::BaseArgs {
inner: Some(msg_inner.as_union_value()),
inner_type: msg::Any::CreateWorkerRes,
..Default::default()
},
)
};
// Has provided source code, execute immediately.
if has_source_code {
worker.execute(&source_code).unwrap();
return ok_buf(exec_cb(worker));
}
let module_specifier = ModuleSpecifier::resolve_url_or_path(specifier)?;
let op =
worker
.execute_mod_async(&module_specifier, false)
.and_then(move |()| {
let mut workers_tl = parent_state.workers.lock().unwrap();
workers_tl.insert(rid, worker.shared());
let builder = &mut FlatBufferBuilder::new();
let msg_inner = msg::CreateWorkerRes::create(
builder,
&msg::CreateWorkerResArgs { rid },
);
Ok(serialize_response(
cmd_id,
builder,
msg::BaseArgs {
inner: Some(msg_inner.as_union_value()),
inner_type: msg::Any::CreateWorkerRes,
..Default::default()
},
))
});
let op = worker
.execute_mod_async(&module_specifier, false)
.and_then(move |()| Ok(exec_cb(worker)));
let result = op.wait()?;
Ok(Op::Sync(result))

View file

@ -118,6 +118,10 @@ function processBlobParts(
return bytes;
}
// A WeakMap holding blob to byte array mapping.
// Ensures it does not impact garbage collection.
export const blobBytesWeakMap = new WeakMap<domTypes.Blob, Uint8Array>();
export class DenoBlob implements domTypes.Blob {
private readonly [bytesSymbol]: Uint8Array;
readonly size: number = 0;
@ -161,6 +165,9 @@ export class DenoBlob implements domTypes.Blob {
this[bytesSymbol] = bytes;
this.size = bytes.byteLength;
this.type = type;
// Register bytes for internal private use.
blobBytesWeakMap.set(this, bytes);
}
slice(start?: number, end?: number, contentType?: string): DenoBlob {

View file

@ -1,5 +1,8 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import * as urlSearchParams from "./url_search_params";
import * as domTypes from "./dom_types";
import { getRandomValues } from "./get_random_values";
import { window } from "./window";
interface URLParts {
protocol: string;
@ -63,6 +66,20 @@ function parse(url: string): URLParts | undefined {
return undefined;
}
// Based on https://github.com/kelektiv/node-uuid
// TODO(kevinkassimo): Use deno_std version once possible.
function generateUUID(): string {
return "00000000-0000-4000-8000-000000000000".replace(
/[0]/g,
(): string =>
// random integer from 0 to 15 as a hex digit.
(getRandomValues(new Uint8Array(1))[0] % 16).toString(16)
);
}
// Keep it outside of URL to avoid any attempts of access.
export const blobURLMap = new Map<string, domTypes.Blob>();
export class URL {
private _parts: URLParts;
private _searchParams!: urlSearchParams.URLSearchParams;
@ -273,4 +290,27 @@ export class URL {
toJSON(): string {
return this.href;
}
// TODO(kevinkassimo): implement MediaSource version in the future.
static createObjectURL(b: domTypes.Blob): string {
const origin = window.location.origin || "http://deno-opaque-origin";
const key = `blob:${origin}/${generateUUID()}`;
blobURLMap.set(key, b);
return key;
}
static revokeObjectURL(url: string): void {
let urlObject;
try {
urlObject = new URL(url);
} catch {
throw new TypeError("Provided URL string is not valid");
}
if (urlObject.protocol !== "blob:") {
return;
}
// Origin match check seems irrelevant for now, unless we implement
// persisten storage for per window.location.origin at some point.
blobURLMap.delete(url);
}
}

View file

@ -6,6 +6,8 @@ import * as flatbuffers from "./flatbuffers";
import { assert, log } from "./util";
import { TextDecoder, TextEncoder } from "./text_encoding";
import { window } from "./window";
import { blobURLMap } from "./url";
import { blobBytesWeakMap } from "./blob";
const encoder = new TextEncoder();
const decoder = new TextDecoder();
@ -22,14 +24,19 @@ export function decodeMessage(dataIntArray: Uint8Array): any {
function createWorker(
specifier: string,
includeDenoNamespace: boolean
includeDenoNamespace: boolean,
hasSourceCode: boolean,
sourceCode: Uint8Array
): number {
const builder = flatbuffers.createBuilder();
const specifier_ = builder.createString(specifier);
const sourceCode_ = builder.createString(sourceCode);
const inner = msg.CreateWorker.createCreateWorker(
builder,
specifier_,
includeDenoNamespace
includeDenoNamespace,
hasSourceCode,
sourceCode_
);
const baseRes = sendSync(builder, msg.Any.CreateWorker, inner);
assert(baseRes != null);
@ -177,11 +184,33 @@ export class WorkerImpl implements Worker {
public onmessageerror?: () => void;
constructor(specifier: string, options?: DenoWorkerOptions) {
let hasSourceCode = false;
let sourceCode = new Uint8Array();
let includeDenoNamespace = true;
if (options && options.noDenoNamespace) {
includeDenoNamespace = false;
}
this.rid = createWorker(specifier, includeDenoNamespace);
// Handle blob URL.
if (specifier.startsWith("blob:")) {
hasSourceCode = true;
const b = blobURLMap.get(specifier);
if (!b) {
throw new Error("No Blob associated with the given URL is found");
}
const blobBytes = blobBytesWeakMap.get(b!);
if (!blobBytes) {
throw new Error("Invalid Blob");
}
sourceCode = blobBytes!;
}
this.rid = createWorker(
specifier,
includeDenoNamespace,
hasSourceCode,
sourceCode
);
this.run();
this.isClosedPromise = hostGetWorkerClosed(this.rid);
this.isClosedPromise.then(

View file

@ -0,0 +1,2 @@
args: run --reload tests/040_worker_blob.ts
output: tests/040_worker_blob.ts.out

6
tests/040_worker_blob.ts Normal file
View file

@ -0,0 +1,6 @@
const b = new Blob(["console.log('code from Blob'); postMessage('DONE')"]);
const blobURL = URL.createObjectURL(b);
const worker = new Worker(blobURL);
worker.onmessage = (): void => {
Deno.exit(0);
};

View file

@ -0,0 +1 @@
code from Blob