Skip to content

Commit de7dc7f

Browse files
authored
perf(fetch): Improve fetch of detaurl (#2479)
* perf(fetch): Improve data url base64 * format * fix: comment position * add comment * add comment * suggestion change * perf: avoid replace * fixup * refactor * fixup * Revert "fixup" This reverts commit 058dc02. * fixup * remove
1 parent d41d58d commit de7dc7f

2 files changed

Lines changed: 62 additions & 52 deletions

File tree

lib/fetch/dataURL.js

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const assert = require('assert')
2-
const { atob } = require('buffer')
32
const { isomorphicDecode } = require('./util')
43

54
const encoder = new TextEncoder()
@@ -8,7 +7,8 @@ const encoder = new TextEncoder()
87
* @see https://mimesniff.spec.whatwg.org/#http-token-code-point
98
*/
109
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-Za-z0-9]+$/
11-
const HTTP_WHITESPACE_REGEX = /(\u000A|\u000D|\u0009|\u0020)/ // eslint-disable-line
10+
const HTTP_WHITESPACE_REGEX = /[\u000A|\u000D|\u0009|\u0020]/ // eslint-disable-line
11+
const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line
1212
/**
1313
* @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
1414
*/
@@ -188,20 +188,26 @@ function stringPercentDecode (input) {
188188
return percentDecode(bytes)
189189
}
190190

191+
function isHexCharByte (byte) {
192+
// 0-9 A-F a-f
193+
return (byte >= 0x30 && byte <= 0x39) || (byte >= 0x41 && byte <= 0x46) || (byte >= 0x61 && byte <= 0x66)
194+
}
195+
191196
// https://url.spec.whatwg.org/#percent-decode
192197
/** @param {Uint8Array} input */
193198
function percentDecode (input) {
199+
const length = input.length
194200
// 1. Let output be an empty byte sequence.
195-
/** @type {number[]} */
196-
const output = []
197-
201+
/** @type {Uint8Array} */
202+
const output = new Uint8Array(length)
203+
let j = 0
198204
// 2. For each byte byte in input:
199-
for (let i = 0; i < input.length; i++) {
205+
for (let i = 0; i < length; ++i) {
200206
const byte = input[i]
201207

202208
// 1. If byte is not 0x25 (%), then append byte to output.
203209
if (byte !== 0x25) {
204-
output.push(byte)
210+
output[j++] = byte
205211

206212
// 2. Otherwise, if byte is 0x25 (%) and the next two bytes
207213
// after byte in input are not in the ranges
@@ -210,9 +216,9 @@ function percentDecode (input) {
210216
// to output.
211217
} else if (
212218
byte === 0x25 &&
213-
!/^[0-9A-Fa-f]{2}$/i.test(String.fromCharCode(input[i + 1], input[i + 2]))
219+
!(isHexCharByte(input[i + 1]) && isHexCharByte(input[i + 2]))
214220
) {
215-
output.push(0x25)
221+
output[j++] = 0x25
216222

217223
// 3. Otherwise:
218224
} else {
@@ -222,15 +228,15 @@ function percentDecode (input) {
222228
const bytePoint = Number.parseInt(nextTwoBytes, 16)
223229

224230
// 2. Append a byte whose value is bytePoint to output.
225-
output.push(bytePoint)
231+
output[j++] = bytePoint
226232

227233
// 3. Skip the next two bytes in input.
228234
i += 2
229235
}
230236
}
231237

232238
// 3. Return output.
233-
return Uint8Array.from(output)
239+
return length === j ? output : output.subarray(0, j)
234240
}
235241

236242
// https://mimesniff.spec.whatwg.org/#parse-a-mime-type
@@ -410,19 +416,25 @@ function parseMIMEType (input) {
410416
/** @param {string} data */
411417
function forgivingBase64 (data) {
412418
// 1. Remove all ASCII whitespace from data.
413-
data = data.replace(/[\u0009\u000A\u000C\u000D\u0020]/g, '') // eslint-disable-line
419+
data = data.replace(ASCII_WHITESPACE_REPLACE_REGEX, '') // eslint-disable-line
414420

421+
let dataLength = data.length
415422
// 2. If data’s code point length divides by 4 leaving
416423
// no remainder, then:
417-
if (data.length % 4 === 0) {
424+
if (dataLength % 4 === 0) {
418425
// 1. If data ends with one or two U+003D (=) code points,
419426
// then remove them from data.
420-
data = data.replace(/=?=$/, '')
427+
if (data.charCodeAt(dataLength - 1) === 0x003D) {
428+
--dataLength
429+
if (data.charCodeAt(dataLength - 1) === 0x003D) {
430+
--dataLength
431+
}
432+
}
421433
}
422434

423435
// 3. If data’s code point length divides by 4 leaving
424436
// a remainder of 1, then return failure.
425-
if (data.length % 4 === 1) {
437+
if (dataLength % 4 === 1) {
426438
return 'failure'
427439
}
428440

@@ -431,18 +443,12 @@ function forgivingBase64 (data) {
431443
// U+002F (/)
432444
// ASCII alphanumeric
433445
// then return failure.
434-
if (/[^+/0-9A-Za-z]/.test(data)) {
446+
if (/[^+/0-9A-Za-z]/.test(data.length === dataLength ? data : data.substring(0, dataLength))) {
435447
return 'failure'
436448
}
437449

438-
const binary = atob(data)
439-
const bytes = new Uint8Array(binary.length)
440-
441-
for (let byte = 0; byte < binary.length; byte++) {
442-
bytes[byte] = binary.charCodeAt(byte)
443-
}
444-
445-
return bytes
450+
const buffer = Buffer.from(data, 'base64')
451+
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
446452
}
447453

448454
// https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
@@ -570,55 +576,54 @@ function serializeAMimeType (mimeType) {
570576

571577
/**
572578
* @see https://fetch.spec.whatwg.org/#http-whitespace
573-
* @param {string} char
579+
* @param {number} char
574580
*/
575581
function isHTTPWhiteSpace (char) {
576-
return char === '\r' || char === '\n' || char === '\t' || char === ' '
582+
// "\r\n\t "
583+
return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x020
577584
}
578585

579586
/**
580587
* @see https://fetch.spec.whatwg.org/#http-whitespace
581588
* @param {string} str
589+
* @param {boolean} [leading=true]
590+
* @param {boolean} [trailing=true]
582591
*/
583592
function removeHTTPWhitespace (str, leading = true, trailing = true) {
584-
let lead = 0
585-
let trail = str.length - 1
586-
593+
let i = 0; let j = str.length
587594
if (leading) {
588-
for (; lead < str.length && isHTTPWhiteSpace(str[lead]); lead++);
595+
while (j > i && isHTTPWhiteSpace(str.charCodeAt(i))) --i
589596
}
590-
591597
if (trailing) {
592-
for (; trail > 0 && isHTTPWhiteSpace(str[trail]); trail--);
598+
while (j > i && isHTTPWhiteSpace(str.charCodeAt(j - 1))) --j
593599
}
594-
595-
return str.slice(lead, trail + 1)
600+
return i === 0 && j === str.length ? str : str.substring(i, j)
596601
}
597602

598603
/**
599604
* @see https://infra.spec.whatwg.org/#ascii-whitespace
600-
* @param {string} char
605+
* @param {number} char
601606
*/
602607
function isASCIIWhitespace (char) {
603-
return char === '\r' || char === '\n' || char === '\t' || char === '\f' || char === ' '
608+
// "\r\n\t\f "
609+
return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x00c || char === 0x020
604610
}
605611

606612
/**
607613
* @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace
614+
* @param {string} str
615+
* @param {boolean} [leading=true]
616+
* @param {boolean} [trailing=true]
608617
*/
609618
function removeASCIIWhitespace (str, leading = true, trailing = true) {
610-
let lead = 0
611-
let trail = str.length - 1
612-
619+
let i = 0; let j = str.length
613620
if (leading) {
614-
for (; lead < str.length && isASCIIWhitespace(str[lead]); lead++);
621+
while (j > i && isASCIIWhitespace(str.charCodeAt(i))) --i
615622
}
616-
617623
if (trailing) {
618-
for (; trail > 0 && isASCIIWhitespace(str[trail]); trail--);
624+
while (j > i && isASCIIWhitespace(str.charCodeAt(j - 1))) --j
619625
}
620-
621-
return str.slice(lead, trail + 1)
626+
return i === 0 && j === str.length ? str : str.substring(i, j)
622627
}
623628

624629
module.exports = {

lib/fetch/util.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -897,22 +897,27 @@ function isReadableStreamLike (stream) {
897897
)
898898
}
899899

900-
const MAXIMUM_ARGUMENT_LENGTH = 65535
901-
902900
/**
903901
* @see https://infra.spec.whatwg.org/#isomorphic-decode
904-
* @param {number[]|Uint8Array} input
902+
* @param {Uint8Array} input
905903
*/
906904
function isomorphicDecode (input) {
907905
// 1. To isomorphic decode a byte sequence input, return a string whose code point
908906
// length is equal to input’s length and whose code points have the same values
909907
// as the values of input’s bytes, in the same order.
910-
911-
if (input.length < MAXIMUM_ARGUMENT_LENGTH) {
912-
return String.fromCharCode(...input)
908+
const length = input.length
909+
if ((2 << 15) - 1 > length) {
910+
return String.fromCharCode.apply(null, input)
913911
}
914-
915-
return input.reduce((previous, current) => previous + String.fromCharCode(current), '')
912+
let result = ''; let i = 0
913+
let addition = (2 << 15) - 1
914+
while (i < length) {
915+
if (i + addition > length) {
916+
addition = length - i
917+
}
918+
result += String.fromCharCode.apply(null, input.subarray(i, i += addition))
919+
}
920+
return result
916921
}
917922

918923
/**

0 commit comments

Comments
 (0)