@@ -177,6 +177,7 @@ const kID = Symbol('id');
177177const kInit = Symbol ( 'init' ) ;
178178const kInfoHeaders = Symbol ( 'sent-info-headers' ) ;
179179const kLocalSettings = Symbol ( 'local-settings' ) ;
180+ const kNativeFields = Symbol ( 'kNativeFields' ) ;
180181const kOptions = Symbol ( 'options' ) ;
181182const kOwner = owner_symbol ;
182183const kOrigin = Symbol ( 'origin' ) ;
@@ -196,7 +197,15 @@ const {
196197 paddingBuffer,
197198 PADDING_BUF_FRAME_LENGTH ,
198199 PADDING_BUF_MAX_PAYLOAD_LENGTH ,
199- PADDING_BUF_RETURN_VALUE
200+ PADDING_BUF_RETURN_VALUE ,
201+ kBitfield,
202+ kSessionPriorityListenerCount,
203+ kSessionFrameErrorListenerCount,
204+ kSessionUint8FieldCount,
205+ kSessionHasRemoteSettingsListeners,
206+ kSessionRemoteSettingsIsUpToDate,
207+ kSessionHasPingListeners,
208+ kSessionHasAltsvcListeners,
200209} = binding ;
201210
202211const {
@@ -372,6 +381,76 @@ function submitRstStream(code) {
372381 }
373382}
374383
384+ // Keep track of the number/presence of JS event listeners. Knowing that there
385+ // are no listeners allows the C++ code to skip calling into JS for an event.
386+ function sessionListenerAdded ( name ) {
387+ switch ( name ) {
388+ case 'ping' :
389+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasPingListeners ;
390+ break ;
391+ case 'altsvc' :
392+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasAltsvcListeners ;
393+ break ;
394+ case 'remoteSettings' :
395+ this [ kNativeFields ] [ kBitfield ] |= 1 << kSessionHasRemoteSettingsListeners ;
396+ break ;
397+ case 'priority' :
398+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] ++ ;
399+ break ;
400+ case 'frameError' :
401+ this [ kNativeFields ] [ kSessionFrameErrorListenerCount ] ++ ;
402+ break ;
403+ }
404+ }
405+
406+ function sessionListenerRemoved ( name ) {
407+ switch ( name ) {
408+ case 'ping' :
409+ if ( this . listenerCount ( name ) > 0 ) return ;
410+ this [ kNativeFields ] [ kBitfield ] &= ~ ( 1 << kSessionHasPingListeners ) ;
411+ break ;
412+ case 'altsvc' :
413+ if ( this . listenerCount ( name ) > 0 ) return ;
414+ this [ kNativeFields ] [ kBitfield ] &= ~ ( 1 << kSessionHasAltsvcListeners ) ;
415+ break ;
416+ case 'remoteSettings' :
417+ if ( this . listenerCount ( name ) > 0 ) return ;
418+ this [ kNativeFields ] [ kBitfield ] &=
419+ ~ ( 1 << kSessionHasRemoteSettingsListeners ) ;
420+ break ;
421+ case 'priority' :
422+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
423+ break ;
424+ case 'frameError' :
425+ this [ kNativeFields ] [ kSessionFrameErrorListenerCount ] -- ;
426+ break ;
427+ }
428+ }
429+
430+ // Also keep track of listeners for the Http2Stream instances, as some events
431+ // are emitted on those objects.
432+ function streamListenerAdded ( name ) {
433+ switch ( name ) {
434+ case 'priority' :
435+ this [ kSession ] [ kNativeFields ] [ kSessionPriorityListenerCount ] ++ ;
436+ break ;
437+ case 'frameError' :
438+ this [ kSession ] [ kNativeFields ] [ kSessionFrameErrorListenerCount ] ++ ;
439+ break ;
440+ }
441+ }
442+
443+ function streamListenerRemoved ( name ) {
444+ switch ( name ) {
445+ case 'priority' :
446+ this [ kSession ] [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
447+ break ;
448+ case 'frameError' :
449+ this [ kSession ] [ kNativeFields ] [ kSessionFrameErrorListenerCount ] -- ;
450+ break ;
451+ }
452+ }
453+
375454function onPing ( payload ) {
376455 const session = this [ kOwner ] ;
377456 if ( session . destroyed )
@@ -430,7 +509,6 @@ function onSettings() {
430509 return ;
431510 session [ kUpdateTimer ] ( ) ;
432511 debugSessionObj ( session , 'new settings received' ) ;
433- session [ kRemoteSettings ] = undefined ;
434512 session . emit ( 'remoteSettings' , session . remoteSettings ) ;
435513}
436514
@@ -854,6 +932,10 @@ function setupHandle(socket, type, options) {
854932 handle . consume ( socket . _handle ) ;
855933
856934 this [ kHandle ] = handle ;
935+ if ( this [ kNativeFields ] )
936+ handle . fields . set ( this [ kNativeFields ] ) ;
937+ else
938+ this [ kNativeFields ] = handle . fields ;
857939
858940 if ( socket . encrypted ) {
859941 this [ kAlpnProtocol ] = socket . alpnProtocol ;
@@ -895,6 +977,7 @@ function finishSessionDestroy(session, error) {
895977 session [ kProxySocket ] = undefined ;
896978 session [ kSocket ] = undefined ;
897979 session [ kHandle ] = undefined ;
980+ session [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
898981 socket [ kSession ] = undefined ;
899982 socket [ kServer ] = undefined ;
900983
@@ -974,6 +1057,7 @@ class Http2Session extends EventEmitter {
9741057 this [ kProxySocket ] = null ;
9751058 this [ kSocket ] = socket ;
9761059 this [ kTimeout ] = null ;
1060+ this [ kHandle ] = undefined ;
9771061
9781062 // Do not use nagle's algorithm
9791063 if ( typeof socket . setNoDelay === 'function' )
@@ -998,6 +1082,11 @@ class Http2Session extends EventEmitter {
9981082 setupFn ( ) ;
9991083 }
10001084
1085+ if ( ! this [ kNativeFields ] )
1086+ this [ kNativeFields ] = new Uint8Array ( kSessionUint8FieldCount ) ;
1087+ this . on ( 'newListener' , sessionListenerAdded ) ;
1088+ this . on ( 'removeListener' , sessionListenerRemoved ) ;
1089+
10011090 debugSession ( type , 'created' ) ;
10021091 }
10031092
@@ -1155,13 +1244,18 @@ class Http2Session extends EventEmitter {
11551244
11561245 // The settings currently in effect for the remote peer.
11571246 get remoteSettings ( ) {
1158- const settings = this [ kRemoteSettings ] ;
1159- if ( settings !== undefined )
1160- return settings ;
1247+ if ( this [ kNativeFields ] [ kBitfield ] &
1248+ ( 1 << kSessionRemoteSettingsIsUpToDate ) ) {
1249+ const settings = this [ kRemoteSettings ] ;
1250+ if ( settings !== undefined ) {
1251+ return settings ;
1252+ }
1253+ }
11611254
11621255 if ( this . destroyed || this . connecting )
11631256 return { } ;
11641257
1258+ this [ kNativeFields ] [ kBitfield ] |= ( 1 << kSessionRemoteSettingsIsUpToDate ) ;
11651259 return this [ kRemoteSettings ] = getSettings ( this [ kHandle ] , true ) ; // Remote
11661260 }
11671261
@@ -1340,6 +1434,12 @@ class ServerHttp2Session extends Http2Session {
13401434 constructor ( options , socket , server ) {
13411435 super ( NGHTTP2_SESSION_SERVER , options , socket ) ;
13421436 this [ kServer ] = server ;
1437+ // This is a bit inaccurate because it does not reflect changes to
1438+ // number of listeners made after the session was created. This should
1439+ // not be an issue in practice. Additionally, the 'priority' event on
1440+ // server instances (or any other object) is fully undocumented.
1441+ this [ kNativeFields ] [ kSessionPriorityListenerCount ] =
1442+ server . listenerCount ( 'priority' ) ;
13431443 }
13441444
13451445 get server ( ) {
@@ -1652,6 +1752,9 @@ class Http2Stream extends Duplex {
16521752 this [ kProxySocket ] = null ;
16531753
16541754 this . on ( 'pause' , streamOnPause ) ;
1755+
1756+ this . on ( 'newListener' , streamListenerAdded ) ;
1757+ this . on ( 'removeListener' , streamListenerRemoved ) ;
16551758 }
16561759
16571760 [ kUpdateTimer ] ( ) {
@@ -2608,7 +2711,7 @@ function sessionOnPriority(stream, parent, weight, exclusive) {
26082711}
26092712
26102713function sessionOnError ( error ) {
2611- if ( this [ kServer ] )
2714+ if ( this [ kServer ] !== undefined )
26122715 this [ kServer ] . emit ( 'sessionError' , error , this ) ;
26132716}
26142717
@@ -2657,8 +2760,10 @@ function connectionListener(socket) {
26572760 const session = new ServerHttp2Session ( options , socket , this ) ;
26582761
26592762 session . on ( 'stream' , sessionOnStream ) ;
2660- session . on ( 'priority' , sessionOnPriority ) ;
26612763 session . on ( 'error' , sessionOnError ) ;
2764+ // Don't count our own internal listener.
2765+ session . on ( 'priority' , sessionOnPriority ) ;
2766+ session [ kNativeFields ] [ kSessionPriorityListenerCount ] -- ;
26622767
26632768 if ( this . timeout )
26642769 session . setTimeout ( this . timeout , sessionOnTimeout ) ;
0 commit comments