@@ -138,40 +138,56 @@ export class BrowserClient extends Client<BrowserClientOptions> {
138138
139139 const { sendClientReports } = this . _options ;
140140
141- // Flush buffered data when the page becomes hidden (e.g. tab switch, navigation, or the page
142- // being discarded). `flush()` emits the `flush` hook, which drains the span (streaming), log and
143- // metric buffers and hands the resulting envelopes to the transport (which uses `keepalive`).
144- // Client report outcomes don't listen to the `flush` hook, so we flush them separately.
145- if ( WINDOW . document ) {
146- WINDOW . document . addEventListener ( 'visibilitychange' , ( ) => {
147- // Devices usually hide the page before they sleep and show it again after they wake up. Checking for drift
148- // at both points makes the time origin correction happen at the sleep, rather than at the next regular timestamp call.
149- timestampInSeconds ( ) ;
150-
151- if ( WINDOW . document . visibilityState === 'hidden' ) {
152- if ( sendClientReports ) {
153- this . _flushOutcomes ( ) ;
141+ // Devices usually hide the page before they sleep and show it again after they wake up. Checking for drift
142+ // at both points makes the time origin correction happen at the sleep, rather than at the next regular timestamp call.
143+ const checkDrift = ( ) : void => {
144+ timestampInSeconds ( ) ;
145+ } ;
146+
147+ // Most bundle-size-efficient way to add and remove listeners: Create a table to avoid repeated
148+ // addeventlistener and removeeventlistener calls. The alternative, using an AbortController, can
149+ // lead to app breakage on old Angular<>Zone.js < 0.14.3 versions.
150+ const listeners : [ EventTarget | undefined , string , ( ) => void ] [ ] = [
151+ // Flush buffered data when the page becomes hidden (e.g. tab switch, navigation, or the page
152+ // being discarded). `flush()` emits the `flush` hook, which drains the span (streaming), log and
153+ // metric buffers and hands the resulting envelopes to the transport (which uses `keepalive`).
154+ // Client report outcomes don't listen to the `flush` hook, so we flush them separately.
155+ [
156+ WINDOW . document ,
157+ 'visibilitychange' ,
158+ ( ) => {
159+ // Devices usually hide the page before they sleep and show it again after they wake up. Checking for drift
160+ // at both points makes the time origin correction happen at the sleep, rather than at the next regular timestamp call.
161+ checkDrift ( ) ;
162+
163+ if ( WINDOW . document . visibilityState === 'hidden' ) {
164+ if ( sendClientReports ) {
165+ this . _flushOutcomes ( ) ;
166+ }
167+ // Defer the flush to a microtask so that visibilitychange listeners registered after this
168+ // one have already run. In particular, browser tracing's background-tab detection ends the
169+ // active pageload/navigation (segment) span when the page is hidden. Deferring ensures that
170+ // segment span has been added to the streaming buffer before we flush, so it is sent
171+ // together with its child spans instead of being orphaned.
172+ queueMicrotask ( ( ) => {
173+ void this . flush ( ) ;
174+ } ) ;
154175 }
155- // Defer the flush to a microtask so that visibilitychange listeners registered after this
156- // one have already run. In particular, browser tracing's background-tab detection ends the
157- // active pageload/navigation (segment) span when the page is hidden. Deferring ensures that
158- // segment span has been added to the streaming buffer before we flush, so it is sent
159- // together with its child spans instead of being orphaned.
160- queueMicrotask ( ( ) => {
161- void this . flush ( ) ;
162- } ) ;
163- }
164- } ) ;
165-
176+ } ,
177+ ] ,
166178 // `freeze` and `resume` are only fired by Chromium browsers. Other browsers never fire them, so these listeners
167179 // are no-ops there.
168- WINDOW . document . addEventListener ( 'freeze' , ( ) => timestampInSeconds ( ) ) ;
169- WINDOW . document . addEventListener ( 'resume' , ( ) => timestampInSeconds ( ) ) ;
170- }
171-
172- // Pages restored from the back/forward cache were paused while they were cached.
173- WINDOW . addEventListener ?.( 'pagehide' , ( ) => timestampInSeconds ( ) ) ;
174- WINDOW . addEventListener ?.( 'pageshow' , ( ) => timestampInSeconds ( ) ) ;
180+ [ WINDOW . document , 'freeze' , checkDrift ] ,
181+ [ WINDOW . document , 'resume' , checkDrift ] ,
182+ // Pages restored from the back/forward cache were paused while they were cached.
183+ [ WINDOW , 'pagehide' , checkDrift ] ,
184+ [ WINDOW , 'pageshow' , checkDrift ] ,
185+ ] ;
186+
187+ listeners . forEach ( ( [ target , name , listener ] ) => target ?. addEventListener ?.( name , listener ) ) ;
188+ this . on ( 'close' , ( ) =>
189+ listeners . forEach ( ( [ target , name , listener ] ) => target ?. removeEventListener ?.( name , listener ) ) ,
190+ ) ;
175191
176192 if ( userInfo ) {
177193 this . on ( 'beforeSendSession' , addAutoIpAddressToSession ) ;
0 commit comments