diff options
author | James M Snell <jasnell@gmail.com> | 2021-08-06 19:26:37 -0700 |
---|---|---|
committer | James M Snell <jasnell@gmail.com> | 2021-08-12 07:23:15 -0700 |
commit | 31d1d0c4c19fea5007eb8c55a4cb1178f295c8ca (patch) | |
tree | 952dace5c1d443f62208be2e1bcd5abe7dfa783e /lib | |
parent | 793c08b8d1b9b1f639e74bf7289acb8fb01b1765 (diff) | |
download | node-new-31d1d0c4c19fea5007eb8c55a4cb1178f295c8ca.tar.gz |
url,buffer: implement URL.createObjectURL
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/39693
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/buffer.js | 2 | ||||
-rw-r--r-- | lib/internal/blob.js | 94 | ||||
-rw-r--r-- | lib/internal/url.js | 79 |
3 files changed, 147 insertions, 28 deletions
diff --git a/lib/buffer.js b/lib/buffer.js index 278a67cbbf..1f4f0a2e89 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -120,6 +120,7 @@ const { const { Blob, + resolveObjectURL, } = require('internal/blob'); FastBuffer.prototype.constructor = Buffer; @@ -1239,6 +1240,7 @@ function atob(input) { module.exports = { Blob, + resolveObjectURL, Buffer, SlowBuffer, transcode, diff --git a/lib/internal/blob.js b/lib/internal/blob.js index d69c9c1798..3c6e841acd 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -7,10 +7,11 @@ const { ObjectDefineProperty, PromiseResolve, PromiseReject, - PromisePrototypeFinally, + SafePromisePrototypeFinally, ReflectConstruct, RegExpPrototypeTest, StringPrototypeToLowerCase, + StringPrototypeSplit, Symbol, SymbolIterator, SymbolToStringTag, @@ -20,7 +21,8 @@ const { const { createBlob: _createBlob, FixedSizeBlobCopyJob, -} = internalBinding('buffer'); + getDataObject, +} = internalBinding('blob'); const { TextDecoder } = require('internal/encoding'); @@ -57,26 +59,37 @@ const { } = require('internal/validators'); const kHandle = Symbol('kHandle'); +const kState = Symbol('kState'); const kType = Symbol('kType'); const kLength = Symbol('kLength'); const kArrayBufferPromise = Symbol('kArrayBufferPromise'); +const kMaxChunkSize = 65536; + const disallowedTypeCharacters = /[^\u{0020}-\u{007E}]/u; let Buffer; let ReadableStream; +let URL; + + +// Yes, lazy loading is annoying but because of circular +// references between the url, internal/blob, and buffer +// modules, lazy loading here makes sure that things work. + +function lazyURL(id) { + URL ??= require('internal/url').URL; + return new URL(id); +} function lazyBuffer() { - if (Buffer === undefined) - Buffer = require('buffer').Buffer; + Buffer ??= require('buffer').Buffer; return Buffer; } function lazyReadableStream(options) { - if (ReadableStream === undefined) { - ReadableStream = - require('internal/webstreams/readablestream').ReadableStream; - } + ReadableStream ??= + require('internal/webstreams/readablestream').ReadableStream; return new ReadableStream(options); } @@ -232,9 +245,9 @@ class Blob { return PromiseReject(new ERR_INVALID_THIS('Blob')); // If there's already a promise in flight for the content, - // reuse it, but only once. After the cached promise resolves - // it will be cleared, allowing it to be garbage collected - // as soon as possible. + // reuse it, but only while it's in flight. After the cached + // promise resolves it will be cleared, allowing it to be + // garbage collected as soon as possible. if (this[kArrayBufferPromise]) return this[kArrayBufferPromise]; @@ -260,7 +273,7 @@ class Blob { resolve(ab); }; this[kArrayBufferPromise] = - PromisePrototypeFinally( + SafePromisePrototypeFinally( promise, () => this[kArrayBufferPromise] = undefined); @@ -268,7 +281,6 @@ class Blob { } /** - * * @returns {Promise<string>} */ async text() { @@ -288,10 +300,20 @@ class Blob { const self = this; return new lazyReadableStream({ - async start(controller) { - const ab = await self.arrayBuffer(); - controller.enqueue(new Uint8Array(ab)); - controller.close(); + async start() { + this[kState] = await self.arrayBuffer(); + }, + + pull(controller) { + if (this[kState].byteLength <= kMaxChunkSize) { + controller.enqueue(new Uint8Array(this[kState])); + controller.close(); + this[kState] = undefined; + } else { + const slice = this[kState].slice(0, kMaxChunkSize); + this[kState] = this[kState].slice(kMaxChunkSize); + controller.enqueue(new Uint8Array(slice)); + } } }); } @@ -315,9 +337,47 @@ ObjectDefineProperty(Blob.prototype, SymbolToStringTag, { value: 'Blob', }); +function resolveObjectURL(url) { + url = `${url}`; + try { + const parsed = new lazyURL(url); + + const split = StringPrototypeSplit(parsed.pathname, ':'); + + if (split.length !== 2) + return; + + const { + 0: base, + 1: id, + } = split; + + if (base !== 'nodedata') + return; + + const ret = getDataObject(id); + + if (ret === undefined) + return; + + const { + 0: handle, + 1: length, + 2: type, + } = ret; + + if (handle !== undefined) + return createBlob(handle, length, type); + } catch { + // If there's an error, it's ignored and nothing is returned + } +} + module.exports = { Blob, ClonedBlob, createBlob, isBlob, + kHandle, + resolveObjectURL, }; diff --git a/lib/internal/url.js b/lib/internal/url.js index 0749e07d6e..043b7311a2 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -42,17 +42,20 @@ const { const { getConstructorOf, removeColors } = require('internal/util'); const { - ERR_ARG_NOT_ITERABLE, - ERR_INVALID_ARG_TYPE, - ERR_INVALID_ARG_VALUE, - ERR_INVALID_FILE_URL_HOST, - ERR_INVALID_FILE_URL_PATH, - ERR_INVALID_THIS, - ERR_INVALID_TUPLE, - ERR_INVALID_URL, - ERR_INVALID_URL_SCHEME, - ERR_MISSING_ARGS -} = require('internal/errors').codes; + codes: { + ERR_ARG_NOT_ITERABLE, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_FILE_URL_HOST, + ERR_INVALID_FILE_URL_PATH, + ERR_INVALID_THIS, + ERR_INVALID_TUPLE, + ERR_INVALID_URL, + ERR_INVALID_URL_SCHEME, + ERR_MISSING_ARGS, + ERR_NO_CRYPTO, + }, +} = require('internal/errors'); const { CHAR_AMPERSAND, CHAR_BACKWARD_SLASH, @@ -100,6 +103,11 @@ const { kSchemeStart } = internalBinding('url'); +const { + storeDataObject, + revokeDataObject, +} = internalBinding('blob'); + const context = Symbol('context'); const cannotBeBase = Symbol('cannot-be-base'); const cannotHaveUsernamePasswordPort = @@ -108,6 +116,24 @@ const special = Symbol('special'); const searchParams = Symbol('query'); const kFormat = Symbol('format'); +let blob; +let cryptoRandom; + +function lazyBlob() { + blob ??= require('internal/blob'); + return blob; +} + +function lazyCryptoRandom() { + try { + cryptoRandom ??= require('internal/crypto/random'); + } catch { + // If Node.js built without crypto support, we'll fall + // through here and handle it later. + } + return cryptoRandom; +} + // https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object const IteratorPrototype = ObjectGetPrototypeOf( ObjectGetPrototypeOf([][SymbolIterator]()) @@ -930,6 +956,37 @@ class URL { toJSON() { return this[kFormat]({}); } + + static createObjectURL(obj) { + const cryptoRandom = lazyCryptoRandom(); + if (cryptoRandom === undefined) + throw new ERR_NO_CRYPTO(); + + // Yes, lazy loading is annoying but because of circular + // references between the url, internal/blob, and buffer + // modules, lazy loading here makes sure that things work. + const blob = lazyBlob(); + if (!blob.isBlob(obj)) + throw new ERR_INVALID_ARG_TYPE('obj', 'Blob', obj); + + const id = cryptoRandom.randomUUID(); + + storeDataObject(id, obj[blob.kHandle], obj.size, obj.type); + + return `blob:nodedata:${id}`; + } + + static revokeObjectURL(url) { + url = `${url}`; + try { + const parsed = new URL(url); + const split = StringPrototypeSplit(parsed.pathname, ':'); + if (split.length === 2) + revokeDataObject(split[1]); + } catch { + // If there's an error, it's ignored. + } + } } ObjectDefineProperties(URL.prototype, { |