@@ -167,6 +167,7 @@ const kID = Symbol('id');
167167const kInit = Symbol ( 'init' ) ;
168168const kInfoHeaders = Symbol ( 'sent-info-headers' ) ;
169169const kLocalSettings = Symbol ( 'local-settings' ) ;
170+ const kNativeFields = Symbol ( 'kNativeFields' ) ;
170171const kOptions = Symbol ( 'options' ) ;
171172const kOwner = owner_symbol ;
172173const kOrigin = Symbol ( 'origin' ) ;
@@ -188,7 +189,15 @@ const {
188189 paddingBuffer,
189190 PADDING_BUF_FRAME_LENGTH ,
190191 PADDING_BUF_MAX_PAYLOAD_LENGTH ,
191- PADDING_BUF_RETURN_VALUE
192+ PADDING_BUF_RETURN_VALUE ,
193+ kBitfield,
194+ kSessionPriorityListenerCount,
195+ kSessionFrameErrorListenerCount,
196+ kSessionUint8FieldCount,
197+ kSessionHasRemoteSettingsListeners,
198+ kSessionRemoteSettingsIsUpToDate,
199+ kSessionHasPingListeners,
200+ kSessionHasAltsvcListeners,
192201} = binding ;
193202
194203const {
@@ -364,6 +373,76 @@ function submitRstStream(code) {
364373 }
365374}
366375
376+ // Keep track of the number/presence of JS event listeners. Knowing that there
377+ // are no listeners allows the C++ code to skip calling into JS for an event.
378+ function sessionListenerAdded ( name ) {
379+ switch ( name ) {
380+ case 'ping' :
381+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasPingListeners ;
382+ break ;
383+ case 'altsvc' :
384+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasAltsvcListeners ;
385+ break ;
386+ case 'remoteSettings' :
387+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasRemoteSettingsListeners ;
388+ break ;
389+ case 'priority' :
390+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] ++ ;
391+ break ;
392+ case 'frameError' :
393+ this [ kNativeFields ] [ kSessionFrameErrorListenerCount ] ++ ;
394+ break ;
395+ }
396+ }
397+
398+ function sessionListenerRemoved ( name ) {
399+ switch ( name ) {
400+ case 'ping' :
401+ if ( this . listenerCount ( name ) > 0 ) return ;
402+ this [ kNativeFields ] [ kBitfield ] &= ~ ( 1 << kSessionHasPingListeners ) ;
403+ break ;
404+ case 'altsvc' :
405+ if ( this . listenerCount ( name ) > 0 ) return ;
406+ this [ kNativeFields ] [ kBitfield ] &= ~ ( 1 << kSessionHasAltsvcListeners ) ;
407+ break ;
408+ case 'remoteSettings' :
409+ if ( this . listenerCount ( name ) > 0 ) return ;
410+ this [ kNativeFields ] [ kBitfield ] &=
411+ ~ ( 1 << kSessionHasRemoteSettingsListeners ) ;
412+ break ;
413+ case 'priority' :
414+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
415+ break ;
416+ case 'frameError' :
417+ this [ kNativeFields ] [ kSessionFrameErrorListenerCount ] -- ;
418+ break ;
419+ }
420+ }
421+
422+ // Also keep track of listeners for the Http2Stream instances, as some events
423+ // are emitted on those objects.
424+ function streamListenerAdded ( name ) {
425+ switch ( name ) {
426+ case 'priority' :
427+ this [ kSession ] [ kNativeFields ] [ kSessionPriorityListenerCount ] ++ ;
428+ break ;
429+ case 'frameError' :
430+ this [ kSession ] [ kNativeFields ] [ kSessionFrameErrorListenerCount ] ++ ;
431+ break ;
432+ }
433+ }
434+
435+ function streamListenerRemoved ( name ) {
436+ switch ( name ) {
437+ case 'priority' :
438+ this [ kSession ] [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
439+ break ;
440+ case 'frameError' :
441+ this [ kSession ] [ kNativeFields ] [ kSessionFrameErrorListenerCount ] -- ;
442+ break ;
443+ }
444+ }
445+
367446function onPing ( payload ) {
368447 const session = this [ kOwner ] ;
369448 if ( session . destroyed )
@@ -421,7 +500,6 @@ function onSettings() {
421500 return ;
422501 session [ kUpdateTimer ] ( ) ;
423502 debugSessionObj ( session , 'new settings received' ) ;
424- session [ kRemoteSettings ] = undefined ;
425503 session . emit ( 'remoteSettings' , session . remoteSettings ) ;
426504}
427505
@@ -863,6 +941,10 @@ function setupHandle(socket, type, options) {
863941 handle . consume ( socket . _handle . _externalStream ) ;
864942
865943 this [ kHandle ] = handle ;
944+ if ( this [ kNativeFields ] )
945+ handle . fields . set ( this [ kNativeFields ] ) ;
946+ else
947+ this [ kNativeFields ] = handle . fields ;
866948
867949 if ( socket . encrypted ) {
868950 this [ kAlpnProtocol ] = socket . alpnProtocol ;
@@ -904,6 +986,7 @@ function finishSessionDestroy(session, error) {
904986 session [ kProxySocket ] = undefined ;
905987 session [ kSocket ] = undefined ;
906988 session [ kHandle ] = undefined ;
989+ session [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
907990 socket [ kSession ] = undefined ;
908991 socket [ kServer ] = undefined ;
909992
@@ -982,6 +1065,7 @@ class Http2Session extends EventEmitter {
9821065 this [ kProxySocket ] = null ;
9831066 this [ kSocket ] = socket ;
9841067 this [ kTimeout ] = null ;
1068+ this [ kHandle ] = undefined ;
9851069
9861070 // Do not use nagle's algorithm
9871071 if ( typeof socket . setNoDelay === 'function' )
@@ -1000,6 +1084,11 @@ class Http2Session extends EventEmitter {
10001084 setupFn ( ) ;
10011085 }
10021086
1087+ if ( ! this [ kNativeFields ] )
1088+ this [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
1089+ this . on ( 'newListener' , sessionListenerAdded ) ;
1090+ this . on ( 'removeListener' , sessionListenerRemoved ) ;
1091+
10031092 debugSession ( type , 'created' ) ;
10041093 }
10051094
@@ -1154,13 +1243,18 @@ class Http2Session extends EventEmitter {
11541243
11551244 // The settings currently in effect for the remote peer.
11561245 get remoteSettings ( ) {
1157- const settings = this [ kRemoteSettings ] ;
1158- if ( settings !== undefined )
1159- return settings ;
1246+ if ( this [ kNativeFields ] [ kBitfield ] &
1247+ ( 1 << kSessionRemoteSettingsIsUpToDate ) ) {
1248+ const settings = this [ kRemoteSettings ] ;
1249+ if ( settings !== undefined ) {
1250+ return settings ;
1251+ }
1252+ }
11601253
11611254 if ( this . destroyed || this . connecting )
11621255 return { } ;
11631256
1257+ this [ kNativeFields ] [ kBitfield ] |= ( 1 << kSessionRemoteSettingsIsUpToDate ) ;
11641258 return this [ kRemoteSettings ] = getSettings ( this [ kHandle ] , true ) ; // Remote
11651259 }
11661260
@@ -1343,6 +1437,12 @@ class ServerHttp2Session extends Http2Session {
13431437 constructor ( options , socket , server ) {
13441438 super ( NGHTTP2_SESSION_SERVER , options , socket ) ;
13451439 this [ kServer ] = server ;
1440+ // This is a bit inaccurate because it does not reflect changes to
1441+ // number of listeners made after the session was created. This should
1442+ // not be an issue in practice. Additionally, the 'priority' event on
1443+ // server instances (or any other object) is fully undocumented.
1444+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] =
1445+ server . listenerCount ( 'priority' ) ;
13461446 }
13471447
13481448 get server ( ) {
@@ -1660,6 +1760,9 @@ class Http2Stream extends Duplex {
16601760 } ;
16611761
16621762 this . on ( 'pause' , streamOnPause ) ;
1763+
1764+ this . on ( 'newListener' , streamListenerAdded ) ;
1765+ this . on ( 'removeListener' , streamListenerRemoved ) ;
16631766 }
16641767
16651768 [ kUpdateTimer ] ( ) {
@@ -2633,7 +2736,7 @@ function sessionOnPriority(stream, parent, weight, exclusive) {
26332736}
26342737
26352738function sessionOnError ( error ) {
2636- if ( this [ kServer ] )
2739+ if ( this [ kServer ] !== undefined )
26372740 this [ kServer ] . emit ( 'sessionError' , error , this ) ;
26382741}
26392742
@@ -2682,8 +2785,10 @@ function connectionListener(socket) {
26822785 const session = new ServerHttp2Session ( options , socket , this ) ;
26832786
26842787 session . on ( 'stream' , sessionOnStream ) ;
2685- session . on ( 'priority' , sessionOnPriority ) ;
26862788 session . on ( 'error' , sessionOnError ) ;
2789+ // Don't count our own internal listener.
2790+ session . on ( 'priority' , sessionOnPriority ) ;
2791+ session [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
26872792
26882793 if ( this . timeout )
26892794 session . setTimeout ( this . timeout , sessionOnTimeout ) ;
0 commit comments