@@ -108,6 +108,7 @@ const kInit = Symbol('init');
108108const kInfoHeaders = Symbol ( 'sent-info-headers' ) ;
109109const kMaybeDestroy = Symbol ( 'maybe-destroy' ) ;
110110const kLocalSettings = Symbol ( 'local-settings' ) ;
111+ const kNativeFields = Symbol ( 'kNativeFields' ) ;
111112const kOptions = Symbol ( 'options' ) ;
112113const kOwner = Symbol ( 'owner' ) ;
113114const kOrigin = Symbol ( 'origin' ) ;
@@ -130,7 +131,15 @@ const {
130131 paddingBuffer,
131132 PADDING_BUF_FRAME_LENGTH ,
132133 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,
134143} = binding ;
135144
136145const {
@@ -305,6 +314,76 @@ function submitRstStream(code) {
305314 }
306315}
307316
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+
308387function onPing ( payload ) {
309388 const session = this [ kOwner ] ;
310389 if ( session . destroyed )
@@ -394,7 +473,6 @@ function onSettings() {
394473 return ;
395474 session [ kUpdateTimer ] ( ) ;
396475 debugSessionObj ( session , 'new settings received' ) ;
397- session [ kRemoteSettings ] = undefined ;
398476 session . emit ( 'remoteSettings' , session . remoteSettings ) ;
399477}
400478
@@ -845,6 +923,10 @@ function setupHandle(socket, type, options) {
845923 handle . consume ( socket . _handle . _externalStream ) ;
846924
847925 this [ kHandle ] = handle ;
926+ if ( this [ kNativeFields ] )
927+ handle . fields . set ( this [ kNativeFields ] ) ;
928+ else
929+ this [ kNativeFields ] = handle . fields ;
848930
849931 if ( socket . encrypted ) {
850932 this [ kAlpnProtocol ] = socket . alpnProtocol ;
@@ -886,6 +968,7 @@ function finishSessionDestroy(session, error) {
886968 session [ kProxySocket ] = undefined ;
887969 session [ kSocket ] = undefined ;
888970 session [ kHandle ] = undefined ;
971+ session [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
889972 socket [ kSession ] = undefined ;
890973 socket [ kServer ] = undefined ;
891974
@@ -963,6 +1046,7 @@ class Http2Session extends EventEmitter {
9631046 this [ kType ] = type ;
9641047 this [ kProxySocket ] = null ;
9651048 this [ kSocket ] = socket ;
1049+ this [ kHandle ] = undefined ;
9661050
9671051 // Do not use nagle's algorithm
9681052 if ( typeof socket . setNoDelay === 'function' )
@@ -981,6 +1065,11 @@ class Http2Session extends EventEmitter {
9811065 setupFn ( ) ;
9821066 }
9831067
1068+ if ( ! this [ kNativeFields ] )
1069+ this [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
1070+ this . on ( 'newListener' , sessionListenerAdded ) ;
1071+ this . on ( 'removeListener' , sessionListenerRemoved ) ;
1072+
9841073 debugSession ( type , 'created' ) ;
9851074 }
9861075
@@ -1136,13 +1225,18 @@ class Http2Session extends EventEmitter {
11361225
11371226 // The settings currently in effect for the remote peer.
11381227 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+ }
11421235
11431236 if ( this . destroyed || this . connecting )
11441237 return { } ;
11451238
1239+ this [ kNativeFields ] [ kBitfield ] |= ( 1 << kSessionRemoteSettingsIsUpToDate ) ;
11461240 return this [ kRemoteSettings ] = getSettings ( this [ kHandle ] , true ) ; // Remote
11471241 }
11481242
@@ -1330,6 +1424,12 @@ class ServerHttp2Session extends Http2Session {
13301424 constructor ( options , socket , server ) {
13311425 super ( NGHTTP2_SESSION_SERVER , options , socket ) ;
13321426 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' ) ;
13331433 }
13341434
13351435 get server ( ) {
@@ -1668,6 +1768,9 @@ class Http2Stream extends Duplex {
16681768 } ;
16691769
16701770 this . on ( 'pause' , streamOnPause ) ;
1771+
1772+ this . on ( 'newListener' , streamListenerAdded ) ;
1773+ this . on ( 'removeListener' , streamListenerRemoved ) ;
16711774 }
16721775
16731776 [ kUpdateTimer ] ( ) {
@@ -2620,7 +2723,7 @@ function sessionOnPriority(stream, parent, weight, exclusive) {
26202723}
26212724
26222725function sessionOnError ( error ) {
2623- if ( this [ kServer ] )
2726+ if ( this [ kServer ] !== undefined )
26242727 this [ kServer ] . emit ( 'sessionError' , error , this ) ;
26252728}
26262729
@@ -2669,8 +2772,10 @@ function connectionListener(socket) {
26692772 const session = new ServerHttp2Session ( options , socket , this ) ;
26702773
26712774 session . on ( 'stream' , sessionOnStream ) ;
2672- session . on ( 'priority' , sessionOnPriority ) ;
26732775 session . on ( 'error' , sessionOnError ) ;
2776+ // Don't count our own internal listener.
2777+ session . on ( 'priority' , sessionOnPriority ) ;
2778+ session [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
26742779
26752780 if ( this . timeout )
26762781 session . setTimeout ( this . timeout , sessionOnTimeout ) ;
0 commit comments