summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2021-08-06 19:26:37 -0700
committerJames M Snell <jasnell@gmail.com>2021-08-12 07:23:15 -0700
commit31d1d0c4c19fea5007eb8c55a4cb1178f295c8ca (patch)
tree952dace5c1d443f62208be2e1bcd5abe7dfa783e /lib
parent793c08b8d1b9b1f639e74bf7289acb8fb01b1765 (diff)
downloadnode-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.js2
-rw-r--r--lib/internal/blob.js94
-rw-r--r--lib/internal/url.js79
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, {