5
5
MathMax,
6
6
MathMin,
7
7
ObjectDefineProperty,
8
- ObjectSetPrototypeOf,
9
8
PromiseResolve,
9
+ PromiseReject,
10
+ PromisePrototypeFinally,
11
+ ReflectConstruct,
10
12
RegExpPrototypeTest,
11
13
StringPrototypeToLowerCase,
12
14
Symbol,
@@ -16,14 +18,14 @@ const {
16
18
} = primordials ;
17
19
18
20
const {
19
- createBlob,
21
+ createBlob : _createBlob ,
20
22
FixedSizeBlobCopyJob,
21
23
} = internalBinding ( 'buffer' ) ;
22
24
23
25
const { TextDecoder } = require ( 'internal/encoding' ) ;
24
26
25
27
const {
26
- JSTransferable ,
28
+ makeTransferable ,
27
29
kClone,
28
30
kDeserialize,
29
31
} = require ( 'internal/worker/js_transferable' ) ;
@@ -44,6 +46,7 @@ const {
44
46
AbortError,
45
47
codes : {
46
48
ERR_INVALID_ARG_TYPE ,
49
+ ERR_INVALID_THIS ,
47
50
ERR_BUFFER_TOO_LARGE ,
48
51
}
49
52
} = require ( 'internal/errors' ) ;
@@ -56,17 +59,27 @@ const {
56
59
const kHandle = Symbol ( 'kHandle' ) ;
57
60
const kType = Symbol ( 'kType' ) ;
58
61
const kLength = Symbol ( 'kLength' ) ;
62
+ const kArrayBufferPromise = Symbol ( 'kArrayBufferPromise' ) ;
59
63
60
64
const disallowedTypeCharacters = / [ ^ \u{0020} - \u{007E} ] / u;
61
65
62
66
let Buffer ;
67
+ let ReadableStream ;
63
68
64
69
function lazyBuffer ( ) {
65
70
if ( Buffer === undefined )
66
71
Buffer = require ( 'buffer' ) . Buffer ;
67
72
return Buffer ;
68
73
}
69
74
75
+ function lazyReadableStream ( options ) {
76
+ if ( ReadableStream === undefined ) {
77
+ ReadableStream =
78
+ require ( 'internal/webstreams/readablestream' ) . ReadableStream ;
79
+ }
80
+ return new ReadableStream ( options ) ;
81
+ }
82
+
70
83
function isBlob ( object ) {
71
84
return object ?. [ kHandle ] !== undefined ;
72
85
}
@@ -89,16 +102,7 @@ function getSource(source, encoding) {
89
102
return [ source . byteLength , source ] ;
90
103
}
91
104
92
- class InternalBlob extends JSTransferable {
93
- constructor ( handle , length , type = '' ) {
94
- super ( ) ;
95
- this [ kHandle ] = handle ;
96
- this [ kType ] = type ;
97
- this [ kLength ] = length ;
98
- }
99
- }
100
-
101
- class Blob extends JSTransferable {
105
+ class Blob {
102
106
constructor ( sources = [ ] , options = { } ) {
103
107
emitExperimentalWarning ( 'buffer.Blob' ) ;
104
108
if ( sources === null ||
@@ -120,13 +124,15 @@ class Blob extends JSTransferable {
120
124
if ( ! isUint32 ( length ) )
121
125
throw new ERR_BUFFER_TOO_LARGE ( 0xFFFFFFFF ) ;
122
126
123
- super ( ) ;
124
- this [ kHandle ] = createBlob ( sources_ , length ) ;
127
+ this [ kHandle ] = _createBlob ( sources_ , length ) ;
125
128
this [ kLength ] = length ;
126
129
127
130
type = `${ type } ` ;
128
131
this [ kType ] = RegExpPrototypeTest ( disallowedTypeCharacters , type ) ?
129
132
'' : StringPrototypeToLowerCase ( type ) ;
133
+
134
+ // eslint-disable-next-line no-constructor-return
135
+ return makeTransferable ( this ) ;
130
136
}
131
137
132
138
[ kInspect ] ( depth , options ) {
@@ -150,7 +156,7 @@ class Blob extends JSTransferable {
150
156
const length = this [ kLength ] ;
151
157
return {
152
158
data : { handle, type, length } ,
153
- deserializeInfo : 'internal/blob:InternalBlob '
159
+ deserializeInfo : 'internal/blob:ClonedBlob '
154
160
} ;
155
161
}
156
162
@@ -160,11 +166,35 @@ class Blob extends JSTransferable {
160
166
this [ kLength ] = length ;
161
167
}
162
168
163
- get type ( ) { return this [ kType ] ; }
169
+ /**
170
+ * @readonly
171
+ * @type {string }
172
+ */
173
+ get type ( ) {
174
+ if ( ! isBlob ( this ) )
175
+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
176
+ return this [ kType ] ;
177
+ }
164
178
165
- get size ( ) { return this [ kLength ] ; }
179
+ /**
180
+ * @readonly
181
+ * @type {number }
182
+ */
183
+ get size ( ) {
184
+ if ( ! isBlob ( this ) )
185
+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
186
+ return this [ kLength ] ;
187
+ }
166
188
189
+ /**
190
+ * @param {number } [start]
191
+ * @param {number } [end]
192
+ * @param {string } [contentType]
193
+ * @returns {Blob }
194
+ */
167
195
slice ( start = 0 , end = this [ kLength ] , contentType = '' ) {
196
+ if ( ! isBlob ( this ) )
197
+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
168
198
if ( start < 0 ) {
169
199
start = MathMax ( this [ kLength ] + start , 0 ) ;
170
200
} else {
@@ -188,49 +218,106 @@ class Blob extends JSTransferable {
188
218
189
219
const span = MathMax ( end - start , 0 ) ;
190
220
191
- return new InternalBlob (
192
- this [ kHandle ] . slice ( start , start + span ) , span , contentType ) ;
221
+ return createBlob (
222
+ this [ kHandle ] . slice ( start , start + span ) ,
223
+ span ,
224
+ contentType ) ;
193
225
}
194
226
195
- async arrayBuffer ( ) {
227
+ /**
228
+ * @returns {Promise<ArrayBuffer> }
229
+ */
230
+ arrayBuffer ( ) {
231
+ if ( ! isBlob ( this ) )
232
+ return PromiseReject ( new ERR_INVALID_THIS ( 'Blob' ) ) ;
233
+
234
+ // If there's already a promise in flight for the content,
235
+ // reuse it, but only once. After the cached promise resolves
236
+ // it will be cleared, allowing it to be garbage collected
237
+ // as soon as possible.
238
+ if ( this [ kArrayBufferPromise ] )
239
+ return this [ kArrayBufferPromise ] ;
240
+
196
241
const job = new FixedSizeBlobCopyJob ( this [ kHandle ] ) ;
197
242
198
243
const ret = job . run ( ) ;
244
+
245
+ // If the job returns a value immediately, the ArrayBuffer
246
+ // was generated synchronously and should just be returned
247
+ // directly.
199
248
if ( ret !== undefined )
200
249
return PromiseResolve ( ret ) ;
201
250
202
251
const {
203
252
promise,
204
253
resolve,
205
- reject
254
+ reject,
206
255
} = createDeferredPromise ( ) ;
256
+
207
257
job . ondone = ( err , ab ) => {
208
258
if ( err !== undefined )
209
259
return reject ( new AbortError ( ) ) ;
210
260
resolve ( ab ) ;
211
261
} ;
262
+ this [ kArrayBufferPromise ] =
263
+ PromisePrototypeFinally (
264
+ promise ,
265
+ ( ) => this [ kArrayBufferPromise ] = undefined ) ;
212
266
213
- return promise ;
267
+ return this [ kArrayBufferPromise ] ;
214
268
}
215
269
270
+ /**
271
+ *
272
+ * @returns {Promise<string> }
273
+ */
216
274
async text ( ) {
275
+ if ( ! isBlob ( this ) )
276
+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
277
+
217
278
const dec = new TextDecoder ( ) ;
218
279
return dec . decode ( await this . arrayBuffer ( ) ) ;
219
280
}
281
+
282
+ /**
283
+ * @returns {ReadableStream }
284
+ */
285
+ stream ( ) {
286
+ if ( ! isBlob ( this ) )
287
+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
288
+
289
+ const self = this ;
290
+ return new lazyReadableStream ( {
291
+ async start ( controller ) {
292
+ const ab = await self . arrayBuffer ( ) ;
293
+ controller . enqueue ( new Uint8Array ( ab ) ) ;
294
+ controller . close ( ) ;
295
+ }
296
+ } ) ;
297
+ }
298
+ }
299
+
300
+ function ClonedBlob ( ) {
301
+ return makeTransferable ( ReflectConstruct ( function ( ) { } , [ ] , Blob ) ) ;
302
+ }
303
+ ClonedBlob . prototype [ kDeserialize ] = ( ) => { } ;
304
+
305
+ function createBlob ( handle , length , type = '' ) {
306
+ return makeTransferable ( ReflectConstruct ( function ( ) {
307
+ this [ kHandle ] = handle ;
308
+ this [ kType ] = type ;
309
+ this [ kLength ] = length ;
310
+ } , [ ] , Blob ) ) ;
220
311
}
221
312
222
313
ObjectDefineProperty ( Blob . prototype , SymbolToStringTag , {
223
314
configurable : true ,
224
315
value : 'Blob' ,
225
316
} ) ;
226
317
227
- InternalBlob . prototype . constructor = Blob ;
228
- ObjectSetPrototypeOf (
229
- InternalBlob . prototype ,
230
- Blob . prototype ) ;
231
-
232
318
module . exports = {
233
319
Blob,
234
- InternalBlob,
320
+ ClonedBlob,
321
+ createBlob,
235
322
isBlob,
236
323
} ;
0 commit comments