@@ -12,6 +12,7 @@ const {
1212} = require ( './util' )
1313const { webidl } = require ( './webidl' )
1414const assert = require ( 'node:assert' )
15+ const { sort } = require ( './sort' )
1516
1617const kHeadersMap = Symbol ( 'headers map' )
1718const kHeadersSortedMap = Symbol ( 'headers map sorted' )
@@ -120,6 +121,10 @@ function appendHeader (headers, name, value) {
120121 // privileged no-CORS request headers from headers
121122}
122123
124+ function compareHeaderName ( a , b ) {
125+ return a [ 0 ] < b [ 0 ] ? - 1 : 1
126+ }
127+
123128class HeadersList {
124129 /** @type {[string, string][]|null } */
125130 cookies = null
@@ -237,7 +242,7 @@ class HeadersList {
237242
238243 * [ Symbol . iterator ] ( ) {
239244 // use the lowercased name
240- for ( const [ name , { value } ] of this [ kHeadersMap ] ) {
245+ for ( const { 0 : name , 1 : { value } } of this [ kHeadersMap ] ) {
241246 yield [ name , value ]
242247 }
243248 }
@@ -253,6 +258,79 @@ class HeadersList {
253258
254259 return headers
255260 }
261+
262+ // https://fetch.spec.whatwg.org/#convert-header-names-to-a-sorted-lowercase-set
263+ toSortedArray ( ) {
264+ const size = this [ kHeadersMap ] . size
265+ const array = new Array ( size )
266+ // In most cases, you will use the fast-path.
267+ // fast-path: Use binary insertion sort for small arrays.
268+ if ( size <= 32 ) {
269+ if ( size === 0 ) {
270+ // If empty, it is an empty array. To avoid the first index assignment.
271+ return array
272+ }
273+ // Improve performance by unrolling loop and avoiding double-loop.
274+ // Double-loop-less version of the binary insertion sort.
275+ const iterator = this [ kHeadersMap ] [ Symbol . iterator ] ( )
276+ const firstValue = iterator . next ( ) . value
277+ // set [name, value] to first index.
278+ array [ 0 ] = [ firstValue [ 0 ] , firstValue [ 1 ] . value ]
279+ // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
280+ // 3.2.2. Assert: value is non-null.
281+ assert ( firstValue [ 1 ] . value !== null )
282+ for (
283+ let i = 1 , j = 0 , right = 0 , left = 0 , pivot = 0 , x , value ;
284+ i < size ;
285+ ++ i
286+ ) {
287+ // get next value
288+ value = iterator . next ( ) . value
289+ // set [name, value] to current index.
290+ x = array [ i ] = [ value [ 0 ] , value [ 1 ] . value ]
291+ // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
292+ // 3.2.2. Assert: value is non-null.
293+ assert ( x [ 1 ] !== null )
294+ left = 0
295+ right = i
296+ // binary search
297+ while ( left < right ) {
298+ // middle index
299+ pivot = left + ( ( right - left ) >> 1 )
300+ // compare header name
301+ if ( array [ pivot ] [ 0 ] <= x [ 0 ] ) {
302+ left = pivot + 1
303+ } else {
304+ right = pivot
305+ }
306+ }
307+ if ( i !== pivot ) {
308+ j = i
309+ while ( j > left ) {
310+ array [ j ] = array [ -- j ]
311+ }
312+ array [ left ] = x
313+ }
314+ }
315+ /* c8 ignore next 4 */
316+ if ( ! iterator . next ( ) . done ) {
317+ // This is for debugging and will never be called.
318+ throw new TypeError ( 'Unreachable' )
319+ }
320+ return array
321+ } else {
322+ // This case would be a rare occurrence.
323+ // slow-path: fallback
324+ let i = 0
325+ for ( const { 0 : name , 1 : { value } } of this [ kHeadersMap ] ) {
326+ array [ i ++ ] = [ name , value ]
327+ // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
328+ // 3.2.2. Assert: value is non-null.
329+ assert ( value !== null )
330+ }
331+ return sort ( array , compareHeaderName )
332+ }
333+ }
256334}
257335
258336// https://fetch.spec.whatwg.org/#headers-class
@@ -454,27 +532,19 @@ class Headers {
454532
455533 // 2. Let names be the result of convert header names to a sorted-lowercase
456534 // set with all the names of the headers in list.
457- const names = [ ...this [ kHeadersList ] ]
458- const namesLength = names . length
459- if ( namesLength <= 16 ) {
460- // Note: Use insertion sort for small arrays.
461- for ( let i = 1 , value , j = 0 ; i < namesLength ; ++ i ) {
462- value = names [ i ]
463- for ( j = i - 1 ; j >= 0 ; -- j ) {
464- if ( names [ j ] [ 0 ] <= value [ 0 ] ) break
465- names [ j + 1 ] = names [ j ]
466- }
467- names [ j + 1 ] = value
468- }
469- } else {
470- names . sort ( ( a , b ) => a [ 0 ] < b [ 0 ] ? - 1 : 1 )
471- }
535+ const names = this [ kHeadersList ] . toSortedArray ( )
472536
473537 const cookies = this [ kHeadersList ] . cookies
474538
539+ // fast-path
540+ if ( cookies === null ) {
541+ // Note: The non-null assertion of value has already been done by `HeadersList#toSortedArray`
542+ return ( this [ kHeadersList ] [ kHeadersSortedMap ] = names )
543+ }
544+
475545 // 3. For each name of names:
476- for ( let i = 0 ; i < namesLength ; ++ i ) {
477- const [ name , value ] = names [ i ]
546+ for ( let i = 0 ; i < names . length ; ++ i ) {
547+ const { 0 : name , 1 : value } = names [ i ]
478548 // 1. If name is `set-cookie`, then:
479549 if ( name === 'set-cookie' ) {
480550 // 1. Let values be a list of all values of headers in list whose name
@@ -491,17 +561,15 @@ class Headers {
491561 // 1. Let value be the result of getting name from list.
492562
493563 // 2. Assert: value is non-null.
494- assert ( value !== null )
564+ // Note: This operation was done by `HeadersList#toSortedArray`.
495565
496566 // 3. Append (name, value) to headers.
497567 headers . push ( [ name , value ] )
498568 }
499569 }
500570
501- this [ kHeadersList ] [ kHeadersSortedMap ] = headers
502-
503571 // 4. Return headers.
504- return headers
572+ return ( this [ kHeadersList ] [ kHeadersSortedMap ] = headers )
505573 }
506574
507575 [ Symbol . for ( 'nodejs.util.inspect.custom' ) ] ( ) {
@@ -546,6 +614,8 @@ webidl.converters.HeadersInit = function (V) {
546614
547615module . exports = {
548616 fill,
617+ // for test.
618+ compareHeaderName,
549619 Headers,
550620 HeadersList
551621}
0 commit comments