@@ -24,6 +24,8 @@ import type {
2424} from './instrumentation' ;
2525import type { InternalInteraction } from './monitor/types' ;
2626import type { getSession } from './monitor/utils' ;
27+ import { startTimingTracking } from './notifications/event-tracking' ;
28+ import { createHighlightCanvas } from './notifications/outline-overlay' ;
2729
2830let rootContainer : HTMLDivElement | null = null ;
2931let shadowRoot : ShadowRoot | null = null ;
@@ -160,6 +162,22 @@ export interface Options {
160162 */
161163 trackUnnecessaryRenders ?: boolean ;
162164
165+ /**
166+ * Should the FPS meter show in the toolbar
167+ *
168+ * @default true
169+ */
170+ showFPS ?: boolean ;
171+
172+ /**
173+ * Should react scan log internal errors to the console.
174+ *
175+ * Useful if react scan is not behaving expected and you want to provide information to maintainers when submitting an issue https://github.com/aidenybai/react-scan/issues
176+ *
177+ * @default false
178+ */
179+ _debug ?: 'verbose' | false ;
180+
163181 onCommitStart ?: ( ) => void ;
164182 onRender ?: ( fiber : Fiber , renders : Array < Render > ) => void ;
165183 onCommitFinish ?: ( ) => void ;
@@ -197,6 +215,9 @@ export interface StoreType {
197215 fiberRoots : WeakSet < Fiber > ;
198216 reportData : Map < number , RenderData > ;
199217 legacyReportData : Map < string , RenderData > ;
218+ interactionListeningForRenders :
219+ | ( ( fiber : Fiber , renders : Array < Render > ) => void )
220+ | null ;
200221}
201222
202223export type OutlineKey = `${string } -${string } `;
@@ -270,6 +291,7 @@ export const Store: StoreType = {
270291 reportData : new Map < number , RenderData > ( ) ,
271292 legacyReportData : new Map < string , RenderData > ( ) ,
272293 lastReportTime : signal ( 0 ) ,
294+ interactionListeningForRenders : null ,
273295} ;
274296
275297export const ReactScanInternals : Internals = {
@@ -286,6 +308,7 @@ export const ReactScanInternals: Internals = {
286308 // alwaysShowLabels: false,
287309 animationSpeed : 'fast' ,
288310 dangerouslyForceRunInProduction : false ,
311+ showFPS : true ,
289312 // smoothlyAnimateOutlines: true,
290313 // trackUnnecessaryRenders: false,
291314 } ) ,
@@ -465,50 +488,62 @@ export const getIsProduction = () => {
465488} ;
466489
467490export const start = ( ) => {
468- if ( typeof window === 'undefined' ) {
469- return ;
470- }
491+ try {
492+ if ( typeof window === 'undefined' ) {
493+ return ;
494+ }
471495
472- if (
473- getIsProduction ( ) &&
474- ! ReactScanInternals . options . value . dangerouslyForceRunInProduction
475- ) {
476- return ;
477- }
496+ if (
497+ getIsProduction ( ) &&
498+ ! ReactScanInternals . options . value . dangerouslyForceRunInProduction
499+ ) {
500+ return ;
501+ }
478502
479- const localStorageOptions =
480- readLocalStorage < LocalStorageOptions > ( 'react-scan-options' ) ;
503+ const localStorageOptions =
504+ readLocalStorage < LocalStorageOptions > ( 'react-scan-options' ) ;
481505
482- if ( localStorageOptions ) {
483- const validLocalOptions = validateOptions ( localStorageOptions ) ;
506+ if ( localStorageOptions ) {
507+ const validLocalOptions = validateOptions ( localStorageOptions ) ;
484508
485- if ( Object . keys ( validLocalOptions ) . length > 0 ) {
486- ReactScanInternals . options . value = {
487- ...ReactScanInternals . options . value ,
488- ...validLocalOptions ,
489- } ;
509+ if ( Object . keys ( validLocalOptions ) . length > 0 ) {
510+ ReactScanInternals . options . value = {
511+ ...ReactScanInternals . options . value ,
512+ ...validLocalOptions ,
513+ } ;
514+ }
490515 }
491- }
492516
493- const options = getOptions ( ) ;
494-
495- initReactScanInstrumentation ( ( ) => {
496- initToolbar ( ! ! options . value . showToolbar ) ;
497- } ) ;
498-
499- const isUsedInBrowserExtension = typeof window !== 'undefined' ;
500- if ( ! Store . monitor . value && ! isUsedInBrowserExtension ) {
501- setTimeout ( ( ) => {
502- if ( isInstrumentationActive ( ) ) return ;
503- // biome-ignore lint/suspicious/noConsole: Intended debug output
517+ const options = getOptions ( ) ;
518+
519+ initReactScanInstrumentation ( ( ) => {
520+ initToolbar ( ! ! options . value . showToolbar ) ;
521+ } ) ;
522+
523+ const isUsedInBrowserExtension = typeof window !== 'undefined' ;
524+ if ( ! Store . monitor . value && ! isUsedInBrowserExtension ) {
525+ setTimeout ( ( ) => {
526+ if ( isInstrumentationActive ( ) ) return ;
527+ // biome-ignore lint/suspicious/noConsole: Intended debug output
528+ console . error (
529+ '[React Scan] Failed to load. Must import React Scan before React runs.' ,
530+ ) ;
531+ } , 5000 ) ;
532+ }
533+ } catch ( e ) {
534+ if ( ReactScanInternals . options . value . _debug === 'verbose' ) {
504535 console . error (
505- '[React Scan] Failed to load. Must import React Scan before React runs.' ,
536+ '[React Scan Internal Error]' ,
537+ 'Failed to create notifications outline canvas' ,
538+ e ,
506539 ) ;
507- } , 5000 ) ;
540+ }
508541 }
509542} ;
510543
511544const initToolbar = ( showToolbar : boolean ) => {
545+ startTimingTracking ( ) ;
546+ createNotificationsOutlineCanvas ( ) ;
512547 const windowToolbarContainer = window . __REACT_SCAN_TOOLBAR_CONTAINER__ ;
513548
514549 if ( ! showToolbar ) {
@@ -521,6 +556,21 @@ const initToolbar = (showToolbar: boolean) => {
521556 createToolbar ( shadowRoot ) ;
522557} ;
523558
559+ const createNotificationsOutlineCanvas = ( ) => {
560+ try {
561+ const highlightRoot = document . documentElement ;
562+ createHighlightCanvas ( highlightRoot ) ;
563+ } catch ( e ) {
564+ if ( ReactScanInternals . options . value . _debug === 'verbose' ) {
565+ console . error (
566+ '[React Scan Internal Error]' ,
567+ 'Failed to create notifications outline canvas' ,
568+ e ,
569+ ) ;
570+ }
571+ }
572+ } ;
573+
524574export const scan = ( options : Options = { } ) => {
525575 setOptions ( options ) ;
526576 const isInIframe = Store . isInIframe . value ;
0 commit comments