@@ -108,6 +108,7 @@ const kInit = Symbol('init');
108
108
const kInfoHeaders = Symbol ( 'sent-info-headers' ) ;
109
109
const kMaybeDestroy = Symbol ( 'maybe-destroy' ) ;
110
110
const kLocalSettings = Symbol ( 'local-settings' ) ;
111
+ const kNativeFields = Symbol ( 'kNativeFields' ) ;
111
112
const kOptions = Symbol ( 'options' ) ;
112
113
const kOwner = Symbol ( 'owner' ) ;
113
114
const kOrigin = Symbol ( 'origin' ) ;
@@ -130,7 +131,15 @@ const {
130
131
paddingBuffer,
131
132
PADDING_BUF_FRAME_LENGTH ,
132
133
PADDING_BUF_MAX_PAYLOAD_LENGTH ,
133
- PADDING_BUF_RETURN_VALUE
134
+ PADDING_BUF_RETURN_VALUE ,
135
+ kBitfield,
136
+ kSessionPriorityListenerCount,
137
+ kSessionFrameErrorListenerCount,
138
+ kSessionUint8FieldCount,
139
+ kSessionHasRemoteSettingsListeners,
140
+ kSessionRemoteSettingsIsUpToDate,
141
+ kSessionHasPingListeners,
142
+ kSessionHasAltsvcListeners,
134
143
} = binding ;
135
144
136
145
const {
@@ -305,6 +314,76 @@ function submitRstStream(code) {
305
314
}
306
315
}
307
316
317
+ // Keep track of the number/presence of JS event listeners. Knowing that there
318
+ // are no listeners allows the C++ code to skip calling into JS for an event.
319
+ function sessionListenerAdded ( name ) {
320
+ switch ( name ) {
321
+ case 'ping' :
322
+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasPingListeners ;
323
+ break ;
324
+ case 'altsvc' :
325
+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasAltsvcListeners ;
326
+ break ;
327
+ case 'remoteSettings' :
328
+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasRemoteSettingsListeners ;
329
+ break ;
330
+ case 'priority' :
331
+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] ++ ;
332
+ break ;
333
+ case 'frameError' :
334
+ this [ kNativeFields ] [ kSessionFrameErrorListenerCount ] ++ ;
335
+ break ;
336
+ }
337
+ }
338
+
339
+ function sessionListenerRemoved ( name ) {
340
+ switch ( name ) {
341
+ case 'ping' :
342
+ if ( this . listenerCount ( name ) > 0 ) return ;
343
+ this [ kNativeFields ] [ kBitfield ] &= ~ ( 1 << kSessionHasPingListeners ) ;
344
+ break ;
345
+ case 'altsvc' :
346
+ if ( this . listenerCount ( name ) > 0 ) return ;
347
+ this [ kNativeFields ] [ kBitfield ] &= ~ ( 1 << kSessionHasAltsvcListeners ) ;
348
+ break ;
349
+ case 'remoteSettings' :
350
+ if ( this . listenerCount ( name ) > 0 ) return ;
351
+ this [ kNativeFields ] [ kBitfield ] &=
352
+ ~ ( 1 << kSessionHasRemoteSettingsListeners ) ;
353
+ break ;
354
+ case 'priority' :
355
+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
356
+ break ;
357
+ case 'frameError' :
358
+ this [ kNativeFields ] [ kSessionFrameErrorListenerCount ] -- ;
359
+ break ;
360
+ }
361
+ }
362
+
363
+ // Also keep track of listeners for the Http2Stream instances, as some events
364
+ // are emitted on those objects.
365
+ function streamListenerAdded ( name ) {
366
+ switch ( name ) {
367
+ case 'priority' :
368
+ this [ kSession ] [ kNativeFields ] [ kSessionPriorityListenerCount ] ++ ;
369
+ break ;
370
+ case 'frameError' :
371
+ this [ kSession ] [ kNativeFields ] [ kSessionFrameErrorListenerCount ] ++ ;
372
+ break ;
373
+ }
374
+ }
375
+
376
+ function streamListenerRemoved ( name ) {
377
+ switch ( name ) {
378
+ case 'priority' :
379
+ this [ kSession ] [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
380
+ break ;
381
+ case 'frameError' :
382
+ this [ kSession ] [ kNativeFields ] [ kSessionFrameErrorListenerCount ] -- ;
383
+ break ;
384
+ }
385
+ }
386
+
308
387
function onPing ( payload ) {
309
388
const session = this [ kOwner ] ;
310
389
if ( session . destroyed )
@@ -394,7 +473,6 @@ function onSettings() {
394
473
return ;
395
474
session [ kUpdateTimer ] ( ) ;
396
475
debugSessionObj ( session , 'new settings received' ) ;
397
- session [ kRemoteSettings ] = undefined ;
398
476
session . emit ( 'remoteSettings' , session . remoteSettings ) ;
399
477
}
400
478
@@ -845,6 +923,10 @@ function setupHandle(socket, type, options) {
845
923
handle . consume ( socket . _handle . _externalStream ) ;
846
924
847
925
this [ kHandle ] = handle ;
926
+ if ( this [ kNativeFields ] )
927
+ handle . fields . set ( this [ kNativeFields ] ) ;
928
+ else
929
+ this [ kNativeFields ] = handle . fields ;
848
930
849
931
if ( socket . encrypted ) {
850
932
this [ kAlpnProtocol ] = socket . alpnProtocol ;
@@ -886,6 +968,7 @@ function finishSessionDestroy(session, error) {
886
968
session [ kProxySocket ] = undefined ;
887
969
session [ kSocket ] = undefined ;
888
970
session [ kHandle ] = undefined ;
971
+ session [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
889
972
socket [ kSession ] = undefined ;
890
973
socket [ kServer ] = undefined ;
891
974
@@ -963,6 +1046,7 @@ class Http2Session extends EventEmitter {
963
1046
this [ kType ] = type ;
964
1047
this [ kProxySocket ] = null ;
965
1048
this [ kSocket ] = socket ;
1049
+ this [ kHandle ] = undefined ;
966
1050
967
1051
// Do not use nagle's algorithm
968
1052
if ( typeof socket . setNoDelay === 'function' )
@@ -981,6 +1065,11 @@ class Http2Session extends EventEmitter {
981
1065
setupFn ( ) ;
982
1066
}
983
1067
1068
+ if ( ! this [ kNativeFields ] )
1069
+ this [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
1070
+ this . on ( 'newListener' , sessionListenerAdded ) ;
1071
+ this . on ( 'removeListener' , sessionListenerRemoved ) ;
1072
+
984
1073
debugSession ( type , 'created' ) ;
985
1074
}
986
1075
@@ -1136,13 +1225,18 @@ class Http2Session extends EventEmitter {
1136
1225
1137
1226
// The settings currently in effect for the remote peer.
1138
1227
get remoteSettings ( ) {
1139
- const settings = this [ kRemoteSettings ] ;
1140
- if ( settings !== undefined )
1141
- return settings ;
1228
+ if ( this [ kNativeFields ] [ kBitfield ] &
1229
+ ( 1 << kSessionRemoteSettingsIsUpToDate ) ) {
1230
+ const settings = this [ kRemoteSettings ] ;
1231
+ if ( settings !== undefined ) {
1232
+ return settings ;
1233
+ }
1234
+ }
1142
1235
1143
1236
if ( this . destroyed || this . connecting )
1144
1237
return { } ;
1145
1238
1239
+ this [ kNativeFields ] [ kBitfield ] |= ( 1 << kSessionRemoteSettingsIsUpToDate ) ;
1146
1240
return this [ kRemoteSettings ] = getSettings ( this [ kHandle ] , true ) ; // Remote
1147
1241
}
1148
1242
@@ -1330,6 +1424,12 @@ class ServerHttp2Session extends Http2Session {
1330
1424
constructor ( options , socket , server ) {
1331
1425
super ( NGHTTP2_SESSION_SERVER , options , socket ) ;
1332
1426
this [ kServer ] = server ;
1427
+ // This is a bit inaccurate because it does not reflect changes to
1428
+ // number of listeners made after the session was created. This should
1429
+ // not be an issue in practice. Additionally, the 'priority' event on
1430
+ // server instances (or any other object) is fully undocumented.
1431
+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] =
1432
+ server . listenerCount ( 'priority' ) ;
1333
1433
}
1334
1434
1335
1435
get server ( ) {
@@ -1668,6 +1768,9 @@ class Http2Stream extends Duplex {
1668
1768
} ;
1669
1769
1670
1770
this . on ( 'pause' , streamOnPause ) ;
1771
+
1772
+ this . on ( 'newListener' , streamListenerAdded ) ;
1773
+ this . on ( 'removeListener' , streamListenerRemoved ) ;
1671
1774
}
1672
1775
1673
1776
[ kUpdateTimer ] ( ) {
@@ -2620,7 +2723,7 @@ function sessionOnPriority(stream, parent, weight, exclusive) {
2620
2723
}
2621
2724
2622
2725
function sessionOnError ( error ) {
2623
- if ( this [ kServer ] )
2726
+ if ( this [ kServer ] !== undefined )
2624
2727
this [ kServer ] . emit ( 'sessionError' , error , this ) ;
2625
2728
}
2626
2729
@@ -2669,8 +2772,10 @@ function connectionListener(socket) {
2669
2772
const session = new ServerHttp2Session ( options , socket , this ) ;
2670
2773
2671
2774
session . on ( 'stream' , sessionOnStream ) ;
2672
- session . on ( 'priority' , sessionOnPriority ) ;
2673
2775
session . on ( 'error' , sessionOnError ) ;
2776
+ // Don't count our own internal listener.
2777
+ session . on ( 'priority' , sessionOnPriority ) ;
2778
+ session [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
2674
2779
2675
2780
if ( this . timeout )
2676
2781
session . setTimeout ( this . timeout , sessionOnTimeout ) ;
0 commit comments