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+ logger . log ( 'INIT ' , componentDisplayName , activity ) ;
54+
55+ // tslint:disable-next-line: no-unsafe-any
56+ return activity ;
2957 }
3058
3159 logger . warn ( `Unable to profile component ${ componentDisplayName } due to invalid Tracing Integration` ) ;
@@ -34,7 +62,6 @@ const getInitActivity = (componentDisplayName: string, timeout: number): number
3462
3563interface ProfilerProps {
3664 componentDisplayName ?: string ;
37- timeout ?: number ;
3865}
3966
4067interface ProfilerState {
@@ -45,15 +72,23 @@ class Profiler extends React.Component<ProfilerProps, ProfilerState> {
4572 public constructor ( props : ProfilerProps ) {
4673 super ( props ) ;
4774
48- const { componentDisplayName = UNKNOWN_COMPONENT , timeout = DEFAULT_DURATION } = this . props ;
75+ const { componentDisplayName = UNKNOWN_COMPONENT } = this . props ;
4976
5077 this . state = {
51- activity : getInitActivity ( componentDisplayName , timeout ) ,
78+ activity : getInitActivity ( componentDisplayName ) ,
5279 } ;
5380 }
5481
82+ public componentDidMount ( ) : void {
83+ if ( this . state . activity ) {
84+ afterNextFrame ( this . finishProfile ) ;
85+ }
86+ }
87+
5588 public componentWillUnmount ( ) : void {
56- this . finishProfile ( ) ;
89+ if ( this . state . activity ) {
90+ afterNextFrame ( this . finishProfile ) ;
91+ }
5792 }
5893
5994 public finishProfile = ( ) => {
@@ -88,6 +123,9 @@ function withProfiler<P extends object>(
88123
89124 Wrapped . displayName = `profiler(${ componentDisplayName } )` ;
90125
126+ // Copy over static methods from Wrapped component to Profiler HOC
127+ // See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
128+ hoistNonReactStatic ( Wrapped , WrappedComponent ) ;
91129 return Wrapped ;
92130}
93131
0 commit comments