@@ -178,6 +178,7 @@ const kID = Symbol('id');
178
178
const kInit = Symbol ( 'init' ) ;
179
179
const kInfoHeaders = Symbol ( 'sent-info-headers' ) ;
180
180
const kLocalSettings = Symbol ( 'local-settings' ) ;
181
+ const kNativeFields = Symbol ( 'kNativeFields' ) ;
181
182
const kOptions = Symbol ( 'options' ) ;
182
183
const kOwner = owner_symbol ;
183
184
const kOrigin = Symbol ( 'origin' ) ;
@@ -200,7 +201,15 @@ const {
200
201
paddingBuffer,
201
202
PADDING_BUF_FRAME_LENGTH ,
202
203
PADDING_BUF_MAX_PAYLOAD_LENGTH ,
203
- PADDING_BUF_RETURN_VALUE
204
+ PADDING_BUF_RETURN_VALUE ,
205
+ kBitfield,
206
+ kSessionPriorityListenerCount,
207
+ kSessionFrameErrorListenerCount,
208
+ kSessionUint8FieldCount,
209
+ kSessionHasRemoteSettingsListeners,
210
+ kSessionRemoteSettingsIsUpToDate,
211
+ kSessionHasPingListeners,
212
+ kSessionHasAltsvcListeners,
204
213
} = binding ;
205
214
206
215
const {
@@ -376,6 +385,76 @@ function submitRstStream(code) {
376
385
}
377
386
}
378
387
388
+ // Keep track of the number/presence of JS event listeners. Knowing that there
389
+ // are no listeners allows the C++ code to skip calling into JS for an event.
390
+ function sessionListenerAdded ( name ) {
391
+ switch ( name ) {
392
+ case 'ping' :
393
+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasPingListeners ;
394
+ break ;
395
+ case 'altsvc' :
396
+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasAltsvcListeners ;
397
+ break ;
398
+ case 'remoteSettings' :
399
+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasRemoteSettingsListeners ;
400
+ break ;
401
+ case 'priority' :
402
+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] ++ ;
403
+ break ;
404
+ case 'frameError' :
405
+ this [ kNativeFields ] [ kSessionFrameErrorListenerCount ] ++ ;
406
+ break ;
407
+ }
408
+ }
409
+
410
+ function sessionListenerRemoved ( name ) {
411
+ switch ( name ) {
412
+ case 'ping' :
413
+ if ( this . listenerCount ( name ) > 0 ) return ;
414
+ this [ kNativeFields ] [ kBitfield ] &= ~ ( 1 << kSessionHasPingListeners ) ;
415
+ break ;
416
+ case 'altsvc' :
417
+ if ( this . listenerCount ( name ) > 0 ) return ;
418
+ this [ kNativeFields ] [ kBitfield ] &= ~ ( 1 << kSessionHasAltsvcListeners ) ;
419
+ break ;
420
+ case 'remoteSettings' :
421
+ if ( this . listenerCount ( name ) > 0 ) return ;
422
+ this [ kNativeFields ] [ kBitfield ] &=
423
+ ~ ( 1 << kSessionHasRemoteSettingsListeners ) ;
424
+ break ;
425
+ case 'priority' :
426
+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
427
+ break ;
428
+ case 'frameError' :
429
+ this [ kNativeFields ] [ kSessionFrameErrorListenerCount ] -- ;
430
+ break ;
431
+ }
432
+ }
433
+
434
+ // Also keep track of listeners for the Http2Stream instances, as some events
435
+ // are emitted on those objects.
436
+ function streamListenerAdded ( name ) {
437
+ switch ( name ) {
438
+ case 'priority' :
439
+ this [ kSession ] [ kNativeFields ] [ kSessionPriorityListenerCount ] ++ ;
440
+ break ;
441
+ case 'frameError' :
442
+ this [ kSession ] [ kNativeFields ] [ kSessionFrameErrorListenerCount ] ++ ;
443
+ break ;
444
+ }
445
+ }
446
+
447
+ function streamListenerRemoved ( name ) {
448
+ switch ( name ) {
449
+ case 'priority' :
450
+ this [ kSession ] [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
451
+ break ;
452
+ case 'frameError' :
453
+ this [ kSession ] [ kNativeFields ] [ kSessionFrameErrorListenerCount ] -- ;
454
+ break ;
455
+ }
456
+ }
457
+
379
458
function onPing ( payload ) {
380
459
const session = this [ kOwner ] ;
381
460
if ( session . destroyed )
@@ -434,7 +513,6 @@ function onSettings() {
434
513
return ;
435
514
session [ kUpdateTimer ] ( ) ;
436
515
debugSessionObj ( session , 'new settings received' ) ;
437
- session [ kRemoteSettings ] = undefined ;
438
516
session . emit ( 'remoteSettings' , session . remoteSettings ) ;
439
517
}
440
518
@@ -858,6 +936,10 @@ function setupHandle(socket, type, options) {
858
936
handle . consume ( socket . _handle ) ;
859
937
860
938
this [ kHandle ] = handle ;
939
+ if ( this [ kNativeFields ] )
940
+ handle . fields . set ( this [ kNativeFields ] ) ;
941
+ else
942
+ this [ kNativeFields ] = handle . fields ;
861
943
862
944
if ( socket . encrypted ) {
863
945
this [ kAlpnProtocol ] = socket . alpnProtocol ;
@@ -899,6 +981,7 @@ function finishSessionDestroy(session, error) {
899
981
session [ kProxySocket ] = undefined ;
900
982
session [ kSocket ] = undefined ;
901
983
session [ kHandle ] = undefined ;
984
+ session [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
902
985
socket [ kSession ] = undefined ;
903
986
socket [ kServer ] = undefined ;
904
987
@@ -978,6 +1061,7 @@ class Http2Session extends EventEmitter {
978
1061
this [ kProxySocket ] = null ;
979
1062
this [ kSocket ] = socket ;
980
1063
this [ kTimeout ] = null ;
1064
+ this [ kHandle ] = undefined ;
981
1065
982
1066
// Do not use nagle's algorithm
983
1067
if ( typeof socket . setNoDelay === 'function' )
@@ -1002,6 +1086,11 @@ class Http2Session extends EventEmitter {
1002
1086
setupFn ( ) ;
1003
1087
}
1004
1088
1089
+ if ( ! this [ kNativeFields ] )
1090
+ this [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
1091
+ this . on ( 'newListener' , sessionListenerAdded ) ;
1092
+ this . on ( 'removeListener' , sessionListenerRemoved ) ;
1093
+
1005
1094
debugSession ( type , 'created' ) ;
1006
1095
}
1007
1096
@@ -1159,13 +1248,18 @@ class Http2Session extends EventEmitter {
1159
1248
1160
1249
// The settings currently in effect for the remote peer.
1161
1250
get remoteSettings ( ) {
1162
- const settings = this [ kRemoteSettings ] ;
1163
- if ( settings !== undefined )
1164
- return settings ;
1251
+ if ( this [ kNativeFields ] [ kBitfield ] &
1252
+ ( 1 << kSessionRemoteSettingsIsUpToDate ) ) {
1253
+ const settings = this [ kRemoteSettings ] ;
1254
+ if ( settings !== undefined ) {
1255
+ return settings ;
1256
+ }
1257
+ }
1165
1258
1166
1259
if ( this . destroyed || this . connecting )
1167
1260
return { } ;
1168
1261
1262
+ this [ kNativeFields ] [ kBitfield ] |= ( 1 << kSessionRemoteSettingsIsUpToDate ) ;
1169
1263
return this [ kRemoteSettings ] = getSettings ( this [ kHandle ] , true ) ; // Remote
1170
1264
}
1171
1265
@@ -1344,6 +1438,12 @@ class ServerHttp2Session extends Http2Session {
1344
1438
constructor ( options , socket , server ) {
1345
1439
super ( NGHTTP2_SESSION_SERVER , options , socket ) ;
1346
1440
this [ kServer ] = server ;
1441
+ // This is a bit inaccurate because it does not reflect changes to
1442
+ // number of listeners made after the session was created. This should
1443
+ // not be an issue in practice. Additionally, the 'priority' event on
1444
+ // server instances (or any other object) is fully undocumented.
1445
+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] =
1446
+ server . listenerCount ( 'priority' ) ;
1347
1447
}
1348
1448
1349
1449
get server ( ) {
@@ -1656,6 +1756,9 @@ class Http2Stream extends Duplex {
1656
1756
this [ kProxySocket ] = null ;
1657
1757
1658
1758
this . on ( 'pause' , streamOnPause ) ;
1759
+
1760
+ this . on ( 'newListener' , streamListenerAdded ) ;
1761
+ this . on ( 'removeListener' , streamListenerRemoved ) ;
1659
1762
}
1660
1763
1661
1764
[ kUpdateTimer ] ( ) {
@@ -2612,7 +2715,7 @@ function sessionOnPriority(stream, parent, weight, exclusive) {
2612
2715
}
2613
2716
2614
2717
function sessionOnError ( error ) {
2615
- if ( this [ kServer ] )
2718
+ if ( this [ kServer ] !== undefined )
2616
2719
this [ kServer ] . emit ( 'sessionError' , error , this ) ;
2617
2720
}
2618
2721
@@ -2661,8 +2764,10 @@ function connectionListener(socket) {
2661
2764
const session = new ServerHttp2Session ( options , socket , this ) ;
2662
2765
2663
2766
session . on ( 'stream' , sessionOnStream ) ;
2664
- session . on ( 'priority' , sessionOnPriority ) ;
2665
2767
session . on ( 'error' , sessionOnError ) ;
2768
+ // Don't count our own internal listener.
2769
+ session . on ( 'priority' , sessionOnPriority ) ;
2770
+ session [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
2666
2771
2667
2772
if ( this . timeout )
2668
2773
session . setTimeout ( this . timeout , sessionOnTimeout ) ;
0 commit comments