@@ -178,6 +178,7 @@ const kID = Symbol('id');
178178const kInit = Symbol ( 'init' ) ;
179179const kInfoHeaders = Symbol ( 'sent-info-headers' ) ;
180180const kLocalSettings = Symbol ( 'local-settings' ) ;
181+ const kNativeFields = Symbol ( 'kNativeFields' ) ;
181182const kOptions = Symbol ( 'options' ) ;
182183const kOwner = owner_symbol ;
183184const kOrigin = Symbol ( 'origin' ) ;
@@ -200,7 +201,15 @@ const {
200201 paddingBuffer,
201202 PADDING_BUF_FRAME_LENGTH ,
202203 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,
204213} = binding ;
205214
206215const {
@@ -376,6 +385,76 @@ function submitRstStream(code) {
376385 }
377386}
378387
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+
379458function onPing ( payload ) {
380459 const session = this [ kOwner ] ;
381460 if ( session . destroyed )
@@ -434,7 +513,6 @@ function onSettings() {
434513 return ;
435514 session [ kUpdateTimer ] ( ) ;
436515 debugSessionObj ( session , 'new settings received' ) ;
437- session [ kRemoteSettings ] = undefined ;
438516 session . emit ( 'remoteSettings' , session . remoteSettings ) ;
439517}
440518
@@ -858,6 +936,10 @@ function setupHandle(socket, type, options) {
858936 handle . consume ( socket . _handle ) ;
859937
860938 this [ kHandle ] = handle ;
939+ if ( this [ kNativeFields ] )
940+ handle . fields . set ( this [ kNativeFields ] ) ;
941+ else
942+ this [ kNativeFields ] = handle . fields ;
861943
862944 if ( socket . encrypted ) {
863945 this [ kAlpnProtocol ] = socket . alpnProtocol ;
@@ -899,6 +981,7 @@ function finishSessionDestroy(session, error) {
899981 session [ kProxySocket ] = undefined ;
900982 session [ kSocket ] = undefined ;
901983 session [ kHandle ] = undefined ;
984+ session [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
902985 socket [ kSession ] = undefined ;
903986 socket [ kServer ] = undefined ;
904987
@@ -978,6 +1061,7 @@ class Http2Session extends EventEmitter {
9781061 this [ kProxySocket ] = null ;
9791062 this [ kSocket ] = socket ;
9801063 this [ kTimeout ] = null ;
1064+ this [ kHandle ] = undefined ;
9811065
9821066 // Do not use nagle's algorithm
9831067 if ( typeof socket . setNoDelay === 'function' )
@@ -1002,6 +1086,11 @@ class Http2Session extends EventEmitter {
10021086 setupFn ( ) ;
10031087 }
10041088
1089+ if ( ! this [ kNativeFields ] )
1090+ this [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
1091+ this . on ( 'newListener' , sessionListenerAdded ) ;
1092+ this . on ( 'removeListener' , sessionListenerRemoved ) ;
1093+
10051094 debugSession ( type , 'created' ) ;
10061095 }
10071096
@@ -1159,13 +1248,18 @@ class Http2Session extends EventEmitter {
11591248
11601249 // The settings currently in effect for the remote peer.
11611250 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+ }
11651258
11661259 if ( this . destroyed || this . connecting )
11671260 return { } ;
11681261
1262+ this [ kNativeFields ] [ kBitfield ] |= ( 1 << kSessionRemoteSettingsIsUpToDate ) ;
11691263 return this [ kRemoteSettings ] = getSettings ( this [ kHandle ] , true ) ; // Remote
11701264 }
11711265
@@ -1344,6 +1438,12 @@ class ServerHttp2Session extends Http2Session {
13441438 constructor ( options , socket , server ) {
13451439 super ( NGHTTP2_SESSION_SERVER , options , socket ) ;
13461440 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' ) ;
13471447 }
13481448
13491449 get server ( ) {
@@ -1656,6 +1756,9 @@ class Http2Stream extends Duplex {
16561756 this [ kProxySocket ] = null ;
16571757
16581758 this . on ( 'pause' , streamOnPause ) ;
1759+
1760+ this . on ( 'newListener' , streamListenerAdded ) ;
1761+ this . on ( 'removeListener' , streamListenerRemoved ) ;
16591762 }
16601763
16611764 [ kUpdateTimer ] ( ) {
@@ -2612,7 +2715,7 @@ function sessionOnPriority(stream, parent, weight, exclusive) {
26122715}
26132716
26142717function sessionOnError ( error ) {
2615- if ( this [ kServer ] )
2718+ if ( this [ kServer ] !== undefined )
26162719 this [ kServer ] . emit ( 'sessionError' , error , this ) ;
26172720}
26182721
@@ -2661,8 +2764,10 @@ function connectionListener(socket) {
26612764 const session = new ServerHttp2Session ( options , socket , this ) ;
26622765
26632766 session . on ( 'stream' , sessionOnStream ) ;
2664- session . on ( 'priority' , sessionOnPriority ) ;
26652767 session . on ( 'error' , sessionOnError ) ;
2768+ // Don't count our own internal listener.
2769+ session . on ( 'priority' , sessionOnPriority ) ;
2770+ session [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
26662771
26672772 if ( this . timeout )
26682773 session . setTimeout ( this . timeout , sessionOnTimeout ) ;
0 commit comments