11import { getCurrentHub } from '@sentry/browser' ;
22import { Integration , IntegrationClass } from '@sentry/types' ;
33import { logger } from '@sentry/utils' ;
4+ import * as hoistNonReactStatic from 'hoist-non-react-statics' ;
45import * as React from 'react' ;
56
6- export const DEFAULT_DURATION = 30000 ;
77export const UNKNOWN_COMPONENT = 'unknown' ;
88
99const TRACING_GETTER = ( {
1010 id : 'Tracing' ,
1111} as any ) as IntegrationClass < Integration > ;
1212
13- const getInitActivity = ( componentDisplayName : string , timeout : number ) : number | null => {
13+ /**
14+ *
15+ * Based on implementation from Preact:
16+ * https:github.com/preactjs/preact/blob/9a422017fec6dab287c77c3aef63c7b2fef0c7e1/hooks/src/index.js#L301-L313
17+ *
18+ * Schedule a callback to be invoked after the browser has a chance to paint a new frame.
19+ * Do this by combining requestAnimationFrame (rAF) + setTimeout to invoke a callback after
20+ * the next browser frame.
21+ *
22+ * Also, schedule a timeout in parallel to the the rAF to ensure the callback is invoked
23+ * even if RAF doesn't fire (for example if the browser tab is not visible)
24+ *
25+ * This is what we use to tell if a component activity has finished
26+ *
27+ */
28+ function afterNextFrame ( callback : Function ) : void {
29+ let timeout : number | undefined ;
30+ let raf : number ;
31+
32+ const done = ( ) => {
33+ window . clearTimeout ( timeout ) ;
34+ window . cancelAnimationFrame ( raf ) ;
35+ window . setTimeout ( callback ) ;
36+ } ;
37+
38+ raf = window . requestAnimationFrame ( done ) ;
39+ timeout = window . setTimeout ( done , 100 ) ;
40+ }
41+
42+ const getInitActivity = ( componentDisplayName : string ) : number | null => {
1443 const tracingIntegration = getCurrentHub ( ) . getIntegration ( TRACING_GETTER ) ;
1544
1645 if ( tracingIntegration !== null ) {
1746 // tslint:disable-next-line:no-unsafe-any
18- return ( tracingIntegration as any ) . constructor . pushActivity (
19- componentDisplayName ,
20- {
21- data : { } ,
22- description : `<${ componentDisplayName } >` ,
23- op : 'react' ,
24- } ,
25- {
26- autoPopAfter : timeout ,
27- } ,
28- ) ;
47+ const activity = ( tracingIntegration as any ) . constructor . pushActivity ( componentDisplayName , {
48+ data : { } ,
49+ description : `<${ componentDisplayName } >` ,
50+ op : 'react' ,
51+ } ) ;
52+
53+ // tslint:disable-next-line: no-unsafe-any
54+ return activity ;
2955 }
3056
3157 logger . warn ( `Unable to profile component ${ componentDisplayName } due to invalid Tracing Integration` ) ;
@@ -34,7 +60,6 @@ const getInitActivity = (componentDisplayName: string, timeout: number): number
3460
3561interface ProfilerProps {
3662 componentDisplayName ?: string ;
37- timeout ?: number ;
3863}
3964
4065interface ProfilerState {
@@ -45,15 +70,23 @@ class Profiler extends React.Component<ProfilerProps, ProfilerState> {
4570 public constructor ( props : ProfilerProps ) {
4671 super ( props ) ;
4772
48- const { componentDisplayName = UNKNOWN_COMPONENT , timeout = DEFAULT_DURATION } = this . props ;
73+ const { componentDisplayName = UNKNOWN_COMPONENT } = this . props ;
4974
5075 this . state = {
51- activity : getInitActivity ( componentDisplayName , timeout ) ,
76+ activity : getInitActivity ( componentDisplayName ) ,
5277 } ;
5378 }
5479
80+ public componentDidMount ( ) : void {
81+ if ( this . state . activity ) {
82+ afterNextFrame ( this . finishProfile ) ;
83+ }
84+ }
85+
5586 public componentWillUnmount ( ) : void {
56- this . finishProfile ( ) ;
87+ if ( this . state . activity ) {
88+ afterNextFrame ( this . finishProfile ) ;
89+ }
5790 }
5891
5992 public finishProfile = ( ) => {
@@ -88,6 +121,9 @@ function withProfiler<P extends object>(
88121
89122 Wrapped . displayName = `profiler(${ componentDisplayName } )` ;
90123
124+ // Copy over static methods from Wrapped component to Profiler HOC
125+ // See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
126+ hoistNonReactStatic ( Wrapped , WrappedComponent ) ;
91127 return Wrapped ;
92128}
93129
0 commit comments