From 55a7849a5d7ffad4152a131acc83cc62c2985ff1 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 20 Feb 2024 12:16:07 +0800 Subject: [PATCH] Upgrade to quickjs-ng --- qjs-sys | 2 +- sidevm-quickjs/.gitignore | 2 +- sidevm-quickjs/Cargo.toml | 3 +- sidevm-quickjs/bootcode/js/src/index.ts | 1 + .../bootcode/js/src/polyfill-blob.js | 244 ++++++++++++++++++ sidevm-quickjs/src/service.rs | 10 - 6 files changed, 248 insertions(+), 14 deletions(-) create mode 100644 sidevm-quickjs/bootcode/js/src/polyfill-blob.js diff --git a/qjs-sys b/qjs-sys index ad658ee..a29a4ce 160000 --- a/qjs-sys +++ b/qjs-sys @@ -1 +1 @@ -Subproject commit ad658eefc6a8b004e8836420a75fe9a8cde6692d +Subproject commit a29a4cea7df2ff4f697bf57f89e2380e52806b53 diff --git a/sidevm-quickjs/.gitignore b/sidevm-quickjs/.gitignore index 8ccaf91..1875116 100755 --- a/sidevm-quickjs/.gitignore +++ b/sidevm-quickjs/.gitignore @@ -14,4 +14,4 @@ Cargo.lock node_modules yarn.lock /web/dist -web.tar.gz +phatjs-web.tar.gz diff --git a/sidevm-quickjs/Cargo.toml b/sidevm-quickjs/Cargo.toml index f7f5c44..f7bc845 100755 --- a/sidevm-quickjs/Cargo.toml +++ b/sidevm-quickjs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sidevm-quickjs" -version = "0.9.6" +version = "0.10.0" authors = ["[your_name] <[your_email]>"] edition = "2021" @@ -60,7 +60,6 @@ js-url = [] js-http-listen = [] js-hash = ["sha2", "sha3", "blake2"] -stream = ["js/stream"] sidevm = [] web = ["js-sys", "web-sys", "wasm-bindgen", "wasm-bindgen-futures", "reqwest"] mem-stats = ["phala-allocator", "js/pink-allocator"] diff --git a/sidevm-quickjs/bootcode/js/src/index.ts b/sidevm-quickjs/bootcode/js/src/index.ts index f880be8..064a178 100644 --- a/sidevm-quickjs/bootcode/js/src/index.ts +++ b/sidevm-quickjs/bootcode/js/src/index.ts @@ -5,6 +5,7 @@ import "./polyfill-url"; import "./polyfill-xhr"; import "./sidevm"; import "./polyfill-abortcontroller"; +import "./polyfill-blob"; import { Headers } from "headers-polyfill"; globalThis.Headers = Headers; diff --git a/sidevm-quickjs/bootcode/js/src/polyfill-blob.js b/sidevm-quickjs/bootcode/js/src/polyfill-blob.js new file mode 100644 index 0000000..8996e02 --- /dev/null +++ b/sidevm-quickjs/bootcode/js/src/polyfill-blob.js @@ -0,0 +1,244 @@ +// 64 KiB (same size chrome slice theirs blob into Uint8array's) +const POOL_SIZE = 65536; +const { isView } = ArrayBuffer; + +/** + * @param {(Blob | Uint8Array)[]} parts + * @returns {AsyncIterableIterator} + */ +async function * toIterator (parts) { + for (const part of parts) { + if (isView(part)) { + let position = part.byteOffset; + const end = part.byteOffset + part.byteLength; + + while (position !== end) { + const size = Math.min(end - position, POOL_SIZE); + const chunk = part.buffer.slice(position, position + size); + + position += chunk.byteLength; + yield new Uint8Array(chunk); + } + } else { + yield * part.stream(); + } + } +} + +class Blob { + /** @type {Array.<(Blob|Uint8Array)>} */ + __parts = []; + #type = ''; + __size = 0; + #endings = 'transparent'; + + /** + * The Blob() constructor returns a new Blob object. The content + * of the blob consists of the concatenation of the values given + * in the parameter array. + * + * @param {*} blobParts + * @param {{ type?: string, endings?: string }} [options] + */ + constructor (blobParts = [], options = {}) { + if (typeof blobParts !== 'object' || blobParts === null) { + throw new TypeError('Failed to construct \'Blob\': The provided value cannot be converted to a sequence.'); + } + + if (typeof blobParts[Symbol.iterator] !== 'function') { + throw new TypeError('Failed to construct \'Blob\': The object must have a callable @@iterator property.'); + } + + if (typeof options !== 'object' && typeof options !== 'function') { + throw new TypeError('Failed to construct \'Blob\': parameter 2 cannot convert to dictionary.'); + } + + if (options === null) { + options = {}; + } + + + const encoder = new TextEncoder(); + + for (const element of blobParts) { + let part; + + if (isView(element)) { + part = new Uint8Array( + element.buffer.slice( + element.byteOffset, + element.byteOffset + element.byteLength + ) + ); + } else if (element instanceof ArrayBuffer) { + part = new Uint8Array(element.slice(0)); + } else if (element instanceof Blob) { + part = element; + } else { + part = encoder.encode(`${element}`); + } + + const size = isView(part) ? part.byteLength : part.size; + + // Avoid pushing empty parts into the array to better GC them + if (size) { + this.__size += size; + this.__parts.push(part); + } + } + + this.#endings = `${options.endings === undefined ? 'transparent' : options.endings}`; + const type = options.type === undefined ? '' : String(options.type); + + this.#type = /^[\x20-\x7E]*$/.test(type) ? type : ''; + } + + /** + * The Blob interface's size property returns the + * size of the Blob in bytes. + */ + get size () { + return this.__size; + } + + /** + * The type property of a Blob object returns the MIME type of the file. + */ + get type () { + return this.#type; + } + + /** + * The text() method in the Blob interface returns a Promise + * that resolves with a string containing the contents of + * the blob, interpreted as UTF-8. + * + * @return {Promise} + */ + async text () { + // More optimized than using this.arrayBuffer() + // that requires twice as much ram + const decoder = new TextDecoder(); + let str = ''; + + for await (const part of this.stream()) { + str += decoder.decode(part, { stream: true }); + } + + // Remaining + str += decoder.decode(); + + return str; + } + + /** + * The arrayBuffer() method in the Blob interface returns a + * Promise that resolves with the contents of the blob as + * binary data contained in an ArrayBuffer. + * + * @return {Promise} + */ + async arrayBuffer () { + const data = new Uint8Array(this.size); + let offset = 0; + + for await (const chunk of this.stream()) { + data.set(chunk, offset); + offset += chunk.length; + } + + return data.buffer; + } + + stream () { + const it = toIterator(this.__parts); + + return new ReadableStream({ + type: 'bytes', + async pull (ctrl) { + const chunk = await it.next(); + + chunk.done ? ctrl.close() : ctrl.enqueue(chunk.value); + }, + + async cancel () { + await it.return(); + } + }); + } + + /** + * The Blob interface's slice() method creates and returns a + * new Blob object which contains data from a subset of the + * blob on which it's called. + * + * @param {number} [start] + * @param {number} [end] + * @param {string} [type] + */ + slice (start = 0, end = this.size, type = '') { + const { size } = this; + + let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size); + let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size); + + const span = Math.max(relativeEnd - relativeStart, 0); + const parts = this.__parts; + const blobParts = []; + let added = 0; + + for (const part of parts) { + // don't add the overflow to new blobParts + if (added >= span) { + break; + } + + const size = isView(part) ? part.byteLength : part.size; + + if (relativeStart && size <= relativeStart) { + // Skip the beginning and change the relative + // start & end position as we skip the unwanted parts + relativeStart -= size; + relativeEnd -= size; + } else { + let chunk; + + if (isView(part)) { + chunk = part.subarray(relativeStart, Math.min(size, relativeEnd)); + added += chunk.byteLength; + } else { + chunk = part.slice(relativeStart, Math.min(size, relativeEnd)); + added += chunk.size; + } + + relativeEnd -= size; + blobParts.push(chunk); + relativeStart = 0; // All next sequential parts should start at 0 + } + } + + const blob = new Blob([], { type: `${type}` }); + + blob.__size = span; + blob.__parts = blobParts; + + return blob; + } + + get [Symbol.toStringTag] () { + return 'Blob'; + } +} + +Object.defineProperties(Blob.prototype, { + size: { enumerable: true }, + type: { enumerable: true }, + slice: { enumerable: true } +}); + +Object.defineProperty(globalThis, 'Blob', { + enumerable: true, + configurable: true, + writable: true, + value: Blob +}); diff --git a/sidevm-quickjs/src/service.rs b/sidevm-quickjs/src/service.rs index 8798178..a1bb057 100644 --- a/sidevm-quickjs/src/service.rs +++ b/sidevm-quickjs/src/service.rs @@ -132,22 +132,12 @@ impl Default for ServiceState { } } -pub fn ctx_init(ctx: &js::Context) { - unsafe { - let ctx = ctx.as_ptr(); - #[cfg(feature = "stream")] - c::js_stream_init(ctx); - c::js_blob_init(ctx); - }; -} - impl Service { pub(crate) fn new(weak_self: ServiceWeakRef) -> Self { let runtime = js::Runtime::new(); let ctx = runtime.new_context(); let boxed_self = Box::into_raw(Box::new(weak_self)); unsafe { c::JS_SetContextOpaque(ctx.as_ptr(), boxed_self as *mut _) }; - ctx_init(&ctx); setup_host_functions(&ctx).expect("Failed to setup host functions"); let bootcode = Code::Bytecode(bootcode::BOOT_CODE); ctx.eval(&bootcode).expect("Failed to eval bootcode");