1
1
import { getCurrentHub } from '@sentry/browser' ;
2
2
import { Integration , IntegrationClass } from '@sentry/types' ;
3
3
import { logger } from '@sentry/utils' ;
4
+ import * as hoistNonReactStatic from 'hoist-non-react-statics' ;
4
5
import * as React from 'react' ;
5
6
6
- export const DEFAULT_DURATION = 30000 ;
7
7
export const UNKNOWN_COMPONENT = 'unknown' ;
8
8
9
9
const TRACING_GETTER = ( {
10
10
id : 'Tracing' ,
11
11
} as any ) as IntegrationClass < Integration > ;
12
12
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 => {
14
43
const tracingIntegration = getCurrentHub ( ) . getIntegration ( TRACING_GETTER ) ;
15
44
16
45
if ( tracingIntegration !== null ) {
17
46
// 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 ;
29
55
}
30
56
31
57
logger . warn ( `Unable to profile component ${ componentDisplayName } due to invalid Tracing Integration` ) ;
@@ -34,7 +60,6 @@ const getInitActivity = (componentDisplayName: string, timeout: number): number
34
60
35
61
interface ProfilerProps {
36
62
componentDisplayName ?: string ;
37
- timeout ?: number ;
38
63
}
39
64
40
65
interface ProfilerState {
@@ -45,15 +70,23 @@ class Profiler extends React.Component<ProfilerProps, ProfilerState> {
45
70
public constructor ( props : ProfilerProps ) {
46
71
super ( props ) ;
47
72
48
- const { componentDisplayName = UNKNOWN_COMPONENT , timeout = DEFAULT_DURATION } = this . props ;
73
+ const { componentDisplayName = UNKNOWN_COMPONENT } = this . props ;
49
74
50
75
this . state = {
51
- activity : getInitActivity ( componentDisplayName , timeout ) ,
76
+ activity : getInitActivity ( componentDisplayName ) ,
52
77
} ;
53
78
}
54
79
80
+ public componentDidMount ( ) : void {
81
+ if ( this . state . activity ) {
82
+ afterNextFrame ( this . finishProfile ) ;
83
+ }
84
+ }
85
+
55
86
public componentWillUnmount ( ) : void {
56
- this . finishProfile ( ) ;
87
+ if ( this . state . activity ) {
88
+ afterNextFrame ( this . finishProfile ) ;
89
+ }
57
90
}
58
91
59
92
public finishProfile = ( ) => {
@@ -88,6 +121,9 @@ function withProfiler<P extends object>(
88
121
89
122
Wrapped . displayName = `profiler(${ componentDisplayName } )` ;
90
123
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 ) ;
91
127
return Wrapped ;
92
128
}
93
129
0 commit comments