Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
util.js392 linesDownload Raw Back to fileapi
1'use strict'
2
3const {
4  kState,
5  kError,
6  kResult,
7  kAborted,
8  kLastProgressEventFired
9} = require('./symbols')
10const { ProgressEvent } = require('./progressevent')
11const { getEncoding } = require('./encoding')
12const { serializeAMimeType, parseMIMEType } = require('../fetch/data-url')
13const { types } = require('node:util')
14const { StringDecoder } = require('string_decoder')
15const { btoa } = require('node:buffer')
16
17/** @type {PropertyDescriptor} */
18const staticPropertyDescriptors = {
19  enumerable: true,
20  writable: false,
21  configurable: false
22}
23
24/**
25 * @see https://w3c.github.io/FileAPI/#readOperation
26 * @param {import('./filereader').FileReader} fr
27 * @param {import('buffer').Blob} blob
28 * @param {string} type
29 * @param {string?} encodingName
30 */
31function readOperation (fr, blob, type, encodingName) {
32  // 1. If fr’s state is "loading", throw an InvalidStateError
33  //    DOMException.
34  if (fr[kState] === 'loading') {
35    throw new DOMException('Invalid state', 'InvalidStateError')
36  }
37
38  // 2. Set fr’s state to "loading".
39  fr[kState] = 'loading'
40
41  // 3. Set fr’s result to null.
42  fr[kResult] = null
43
44  // 4. Set fr’s error to null.
45  fr[kError] = null
46
47  // 5. Let stream be the result of calling get stream on blob.
48  /** @type {import('stream/web').ReadableStream} */
49  const stream = blob.stream()
50
51  // 6. Let reader be the result of getting a reader from stream.
52  const reader = stream.getReader()
53
54  // 7. Let bytes be an empty byte sequence.
55  /** @type {Uint8Array[]} */
56  const bytes = []
57
58  // 8. Let chunkPromise be the result of reading a chunk from
59  //    stream with reader.
60  let chunkPromise = reader.read()
61
62  // 9. Let isFirstChunk be true.
63  let isFirstChunk = true
64
65  // 10. In parallel, while true:
66  // Note: "In parallel" just means non-blocking
67  // Note 2: readOperation itself cannot be async as double
68  // reading the body would then reject the promise, instead
69  // of throwing an error.
70  ;(async () => {
71    while (!fr[kAborted]) {
72      // 1. Wait for chunkPromise to be fulfilled or rejected.
73      try {
74        const { done, value } = await chunkPromise
75
76        // 2. If chunkPromise is fulfilled, and isFirstChunk is
77        //    true, queue a task to fire a progress event called
78        //    loadstart at fr.
79        if (isFirstChunk && !fr[kAborted]) {
80          queueMicrotask(() => {
81            fireAProgressEvent('loadstart', fr)
82          })
83        }
84
85        // 3. Set isFirstChunk to false.
86        isFirstChunk = false
87
88        // 4. If chunkPromise is fulfilled with an object whose
89        //    done property is false and whose value property is
90        //    a Uint8Array object, run these steps:
91        if (!done && types.isUint8Array(value)) {
92          // 1. Let bs be the byte sequence represented by the
93          //    Uint8Array object.
94
95          // 2. Append bs to bytes.
96          bytes.push(value)
97
98          // 3. If roughly 50ms have passed since these steps
99          //    were last invoked, queue a task to fire a
100          //    progress event called progress at fr.
101          if (
102            (
103              fr[kLastProgressEventFired] === undefined ||
104              Date.now() - fr[kLastProgressEventFired] >= 50
105            ) &&
106            !fr[kAborted]
107          ) {
108            fr[kLastProgressEventFired] = Date.now()
109            queueMicrotask(() => {
110              fireAProgressEvent('progress', fr)
111            })
112          }
113
114          // 4. Set chunkPromise to the result of reading a
115          //    chunk from stream with reader.
116          chunkPromise = reader.read()
117        } else if (done) {
118          // 5. Otherwise, if chunkPromise is fulfilled with an
119          //    object whose done property is true, queue a task
120          //    to run the following steps and abort this algorithm:
121          queueMicrotask(() => {
122            // 1. Set fr’s state to "done".
123            fr[kState] = 'done'
124
125            // 2. Let result be the result of package data given
126            //    bytes, type, blob’s type, and encodingName.
127            try {
128              const result = packageData(bytes, type, blob.type, encodingName)
129
130              // 4. Else:
131
132              if (fr[kAborted]) {
133                return
134              }
135
136              // 1. Set fr’s result to result.
137              fr[kResult] = result
138
139              // 2. Fire a progress event called load at the fr.
140              fireAProgressEvent('load', fr)
141            } catch (error) {
142              // 3. If package data threw an exception error:
143
144              // 1. Set fr’s error to error.
145              fr[kError] = error
146
147              // 2. Fire a progress event called error at fr.
148              fireAProgressEvent('error', fr)
149            }
150
151            // 5. If fr’s state is not "loading", fire a progress
152            //    event called loadend at the fr.
153            if (fr[kState] !== 'loading') {
154              fireAProgressEvent('loadend', fr)
155            }
156          })
157
158          break
159        }
160      } catch (error) {
161        if (fr[kAborted]) {
162          return
163        }
164
165        // 6. Otherwise, if chunkPromise is rejected with an
166        //    error error, queue a task to run the following
167        //    steps and abort this algorithm:
168        queueMicrotask(() => {
169          // 1. Set fr’s state to "done".
170          fr[kState] = 'done'
171
172          // 2. Set fr’s error to error.
173          fr[kError] = error
174
175          // 3. Fire a progress event called error at fr.
176          fireAProgressEvent('error', fr)
177
178          // 4. If fr’s state is not "loading", fire a progress
179          //    event called loadend at fr.
180          if (fr[kState] !== 'loading') {
181            fireAProgressEvent('loadend', fr)
182          }
183        })
184
185        break
186      }
187    }
188  })()
189}
190
191/**
192 * @see https://w3c.github.io/FileAPI/#fire-a-progress-event
193 * @see https://dom.spec.whatwg.org/#concept-event-fire
194 * @param {string} e The name of the event
195 * @param {import('./filereader').FileReader} reader
196 */
197function fireAProgressEvent (e, reader) {
198  // The progress event e does not bubble. e.bubbles must be false
199  // The progress event e is NOT cancelable. e.cancelable must be false
200  const event = new ProgressEvent(e, {
201    bubbles: false,
202    cancelable: false
203  })
204
205  reader.dispatchEvent(event)
206}
207
208/**
209 * @see https://w3c.github.io/FileAPI/#blob-package-data
210 * @param {Uint8Array[]} bytes
211 * @param {string} type
212 * @param {string?} mimeType
213 * @param {string?} encodingName
214 */
215function packageData (bytes, type, mimeType, encodingName) {
216  // 1. A Blob has an associated package data algorithm, given
217  //    bytes, a type, a optional mimeType, and a optional
218  //    encodingName, which switches on type and runs the
219  //    associated steps:
220
221  switch (type) {
222    case 'DataURL': {
223      // 1. Return bytes as a DataURL [RFC2397] subject to
224      //    the considerations below:
225      //  * Use mimeType as part of the Data URL if it is
226      //    available in keeping with the Data URL
227      //    specification [RFC2397].
228      //  * If mimeType is not available return a Data URL
229      //    without a media-type. [RFC2397].
230
231      // https://datatracker.ietf.org/doc/html/rfc2397#section-3
232      // dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
233      // mediatype  := [ type "/" subtype ] *( ";" parameter )
234      // data       := *urlchar
235      // parameter  := attribute "=" value
236      let dataURL = 'data:'
237
238      const parsed = parseMIMEType(mimeType || 'application/octet-stream')
239
240      if (parsed !== 'failure') {
241        dataURL += serializeAMimeType(parsed)
242      }
243
244      dataURL += ';base64,'
245
246      const decoder = new StringDecoder('latin1')
247
248      for (const chunk of bytes) {
249        dataURL += btoa(decoder.write(chunk))
250      }
251
252      dataURL += btoa(decoder.end())
253
254      return dataURL
255    }
256    case 'Text': {
257      // 1. Let encoding be failure
258      let encoding = 'failure'
259
260      // 2. If the encodingName is present, set encoding to the
261      //    result of getting an encoding from encodingName.
262      if (encodingName) {
263        encoding = getEncoding(encodingName)
264      }
265
266      // 3. If encoding is failure, and mimeType is present:
267      if (encoding === 'failure' && mimeType) {
268        // 1. Let type be the result of parse a MIME type
269        //    given mimeType.
270        const type = parseMIMEType(mimeType)
271
272        // 2. If type is not failure, set encoding to the result
273        //    of getting an encoding from type’s parameters["charset"].
274        if (type !== 'failure') {
275          encoding = getEncoding(type.parameters.get('charset'))
276        }
277      }
278
279      // 4. If encoding is failure, then set encoding to UTF-8.
280      if (encoding === 'failure') {
281        encoding = 'UTF-8'
282      }
283
284      // 5. Decode bytes using fallback encoding encoding, and
285      //    return the result.
286      return decode(bytes, encoding)
287    }
288    case 'ArrayBuffer': {
289      // Return a new ArrayBuffer whose contents are bytes.
290      const sequence = combineByteSequences(bytes)
291
292      return sequence.buffer
293    }
294    case 'BinaryString': {
295      // Return bytes as a binary string, in which every byte
296      //  is represented by a code unit of equal value [0..255].
297      let binaryString = ''
298
299      const decoder = new StringDecoder('latin1')
300
301      for (const chunk of bytes) {
302        binaryString += decoder.write(chunk)
303      }
304
305      binaryString += decoder.end()
306
307      return binaryString
308    }
309  }
310}
311
312/**
313 * @see https://encoding.spec.whatwg.org/#decode
314 * @param {Uint8Array[]} ioQueue
315 * @param {string} encoding
316 */
317function decode (ioQueue, encoding) {
318  const bytes = combineByteSequences(ioQueue)
319
320  // 1. Let BOMEncoding be the result of BOM sniffing ioQueue.
321  const BOMEncoding = BOMSniffing(bytes)
322
323  let slice = 0
324
325  // 2. If BOMEncoding is non-null:
326  if (BOMEncoding !== null) {
327    // 1. Set encoding to BOMEncoding.
328    encoding = BOMEncoding
329
330    // 2. Read three bytes from ioQueue, if BOMEncoding is
331    //    UTF-8; otherwise read two bytes.
332    //    (Do nothing with those bytes.)
333    slice = BOMEncoding === 'UTF-8' ? 3 : 2
334  }
335
336  // 3. Process a queue with an instance of encoding’s
337  //    decoder, ioQueue, output, and "replacement".
338
339  // 4. Return output.
340
341  const sliced = bytes.slice(slice)
342  return new TextDecoder(encoding).decode(sliced)
343}
344
345/**
346 * @see https://encoding.spec.whatwg.org/#bom-sniff
347 * @param {Uint8Array} ioQueue
348 */
349function BOMSniffing (ioQueue) {
350  // 1. Let BOM be the result of peeking 3 bytes from ioQueue,
351  //    converted to a byte sequence.
352  const [a, b, c] = ioQueue
353
354  // 2. For each of the rows in the table below, starting with
355  //    the first one and going down, if BOM starts with the
356  //    bytes given in the first column, then return the
357  //    encoding given in the cell in the second column of that
358  //    row. Otherwise, return null.
359  if (a === 0xEF && b === 0xBB && c === 0xBF) {
360    return 'UTF-8'
361  } else if (a === 0xFE && b === 0xFF) {
362    return 'UTF-16BE'
363  } else if (a === 0xFF && b === 0xFE) {
364    return 'UTF-16LE'
365  }
366
367  return null
368}
369
370/**
371 * @param {Uint8Array[]} sequences
372 */
373function combineByteSequences (sequences) {
374  const size = sequences.reduce((a, b) => {
375    return a + b.byteLength
376  }, 0)
377
378  let offset = 0
379
380  return sequences.reduce((a, b) => {
381    a.set(b, offset)
382    offset += b.byteLength
383    return a
384  }, new Uint8Array(size))
385}
386
387module.exports = {
388  staticPropertyDescriptors,
389  readOperation,
390  fireAProgressEvent
391}
392 
codekingpro/portable-devtools · Team Ai