@buoy-gg/highlight-updates 2.1.10 → 2.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/highlight-updates/HighlightUpdatesOverlay.js +285 -1
- package/lib/commonjs/highlight-updates/components/HighlightFilterView.js +1371 -1
- package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +591 -1
- package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +267 -1
- package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +178 -1
- package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +303 -1
- package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +500 -1
- package/lib/commonjs/highlight-updates/components/RenderDetailView.js +830 -1
- package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +894 -1
- package/lib/commonjs/highlight-updates/components/RenderListItem.js +220 -1
- package/lib/commonjs/highlight-updates/components/StatsDisplay.js +70 -1
- package/lib/commonjs/highlight-updates/components/index.js +97 -1
- package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +1435 -1
- package/lib/commonjs/highlight-updates/utils/PerformanceLogger.js +359 -1
- package/lib/commonjs/highlight-updates/utils/ProfilerInterceptor.js +371 -1
- package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +1828 -1
- package/lib/commonjs/highlight-updates/utils/RenderTracker.js +903 -1
- package/lib/commonjs/highlight-updates/utils/ViewTypeMapper.js +264 -1
- package/lib/commonjs/highlight-updates/utils/renderExportFormatter.js +58 -1
- package/lib/commonjs/index.js +311 -1
- package/lib/commonjs/preset.js +278 -1
- package/lib/module/highlight-updates/HighlightUpdatesOverlay.js +278 -1
- package/lib/module/highlight-updates/components/HighlightFilterView.js +1365 -1
- package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +585 -1
- package/lib/module/highlight-updates/components/IdentifierBadge.js +259 -1
- package/lib/module/highlight-updates/components/IsolatedRenderList.js +174 -1
- package/lib/module/highlight-updates/components/ModalHeaderContent.js +298 -1
- package/lib/module/highlight-updates/components/RenderCauseBadge.js +491 -1
- package/lib/module/highlight-updates/components/RenderDetailView.js +826 -1
- package/lib/module/highlight-updates/components/RenderHistoryViewer.js +888 -1
- package/lib/module/highlight-updates/components/RenderListItem.js +215 -1
- package/lib/module/highlight-updates/components/StatsDisplay.js +67 -1
- package/lib/module/highlight-updates/components/index.js +16 -1
- package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +1431 -1
- package/lib/module/highlight-updates/utils/PerformanceLogger.js +353 -1
- package/lib/module/highlight-updates/utils/ProfilerInterceptor.js +358 -1
- package/lib/module/highlight-updates/utils/RenderCauseDetector.js +1818 -1
- package/lib/module/highlight-updates/utils/RenderTracker.js +900 -1
- package/lib/module/highlight-updates/utils/ViewTypeMapper.js +255 -1
- package/lib/module/highlight-updates/utils/renderExportFormatter.js +54 -1
- package/lib/module/index.js +71 -1
- package/lib/module/preset.js +272 -1
- package/lib/typescript/highlight-updates/HighlightUpdatesOverlay.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/components/index.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/PerformanceLogger.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/ProfilerInterceptor.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/ViewTypeMapper.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/renderExportFormatter.d.ts.map +1 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/package.json +7 -7
|
@@ -1 +1,1435 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _ProfilerInterceptor=require("./ProfilerInterceptor"),_RenderTracker=require("./RenderTracker"),_PerformanceLogger=require("./PerformanceLogger"),_RenderCauseDetector=require("./RenderCauseDetector"),_sharedUi=require("@buoy-gg/shared-ui");let globalEnabled=!1,backgroundTrackingEnabled=!1,initialized=!1,hook=null,highlightCallback=null,badgePressCallback=null,traceUpdatesUnsubscribe=null,isProcessing=!1,isFrozen=!1;const freezeListeners=new Set,stateListeners=new Set,nodeRenderCounts=new Map,COLORS=["#37afa9","#63b19e","#80b393","#97b488","#abb67d","#beb771","#cfb965","#dfba57","#efbb49","#febc38"],DEBUG=!1;function debugLog(e,n){}function getPublicInstance(e){if(!e)return null;const n=e;return n.canonical?.publicInstance?n.canonical.publicInstance:n.canonical&&"function"==typeof n.canonical.measure?n.canonical:"function"==typeof n.measure?n:null}function getNativeTag(e){if(null==e)return null;const n=e;if(null!=n.__nativeTag)return n.__nativeTag;if(null!=n._nativeTag)return n._nativeTag;if(null!=n.nativeTag)return n.nativeTag;if(n.canonical){if(null!=n.canonical.__nativeTag)return n.canonical.__nativeTag;if(null!=n.canonical._nativeTag)return n.canonical._nativeTag}return null}function extractComponentInfo(e){const n=e,t={viewType:"Unknown"};n?.canonical?.viewConfig?.uiViewClassName?t.viewType=n.canonical.viewConfig.uiViewClassName:n?.viewConfig?.uiViewClassName&&(t.viewType=n.viewConfig.uiViewClassName);const r=n?.canonical?.internalInstanceHandle||n?._internalInstanceHandle||n?._internalFiberInstanceHandleDEV,i=n?.canonical?.currentProps,a=n?.canonical?.pendingProps,o=r?.pendingProps,s=r?.memoizedProps;return t.testID=i?.testID||a?.testID||o?.testID||s?.testID||void 0,t.nativeID=i?.nativeID||a?.nativeID||o?.nativeID||s?.nativeID||void 0,t.accessibilityLabel=i?.accessibilityLabel||a?.accessibilityLabel||o?.accessibilityLabel||s?.accessibilityLabel||void 0,t.componentName=getOwningComponentName(r)||void 0,t}function describeNodeForLog(e){const n=e,t=getPublicInstance(e),r={nativeTag:(getNativeTag(e)||getNativeTag(t))??"unknown",type:n?.canonical?"Fabric":"Legacy",hasMeasure:"function"==typeof t?.measure};if(n){if(r.stateNodeKeys=Object.keys(n).slice(0,20),n.node&&(r.nodeKeys=Object.keys(n.node).slice(0,20)),n.viewConfig&&(r.viewConfig={uiViewClassName:n.viewConfig.uiViewClassName,validAttributes:n.viewConfig.validAttributes?Object.keys(n.viewConfig.validAttributes).slice(0,10):void 0}),n.canonical){const e=n.canonical;if(r.canonicalKeys=Object.keys(e).slice(0,20),e.viewConfig&&(r.canonicalViewConfig={uiViewClassName:e.viewConfig.uiViewClassName}),null!=e.nativeTag&&(r.canonicalNativeTag=e.nativeTag),e.internalInstanceHandle){const n=e.internalInstanceHandle;if(r.fiberKeys=Object.keys(n).slice(0,30),n.type&&(r.fiberType="function"==typeof n.type?n.type.name||n.type.displayName||"function":n.type),n.elementType&&(r.fiberElementType="function"==typeof n.elementType?n.elementType.name||n.elementType.displayName||"function":n.elementType),n._debugOwner){const e=n._debugOwner;r.fiberDebugOwner=e.type?.name||e.type?.displayName||e.elementType?.name||"unknown"}n._debugSource&&(r.fiberDebugSource=n._debugSource),null!=n.tag&&(r.fiberTag=n.tag),n.pendingProps&&(n.pendingProps.nativeID&&(r.fiberPendingNativeID=n.pendingProps.nativeID),n.pendingProps.testID&&(r.fiberPendingTestID=n.pendingProps.testID)),n.memoizedProps&&(n.memoizedProps.nativeID&&(r.fiberMemoizedNativeID=n.memoizedProps.nativeID),n.memoizedProps.testID&&(r.fiberMemoizedTestID=n.memoizedProps.testID))}if(e.publicInstance){const n=e.publicInstance;r.publicInstanceKeys=Object.keys(n).slice(0,20),null!=n.nativeID&&(r.nativeID=n.nativeID),null!=n._nativeID&&(r._nativeID=n._nativeID),n.props&&(r.publicInstanceProps=Object.keys(n.props).slice(0,15),n.props.nativeID&&(r.propsNativeID=n.props.nativeID),n.props.testID&&(r.propsTestID=n.props.testID),n.props.accessibilityLabel&&(r.accessibilityLabel=n.props.accessibilityLabel))}e.currentProps&&(r.currentPropsKeys=Object.keys(e.currentProps).slice(0,15),null!=e.currentProps.nativeID&&(r.currentPropsNativeID=e.currentProps.nativeID),null!=e.currentProps.testID&&(r.currentPropsTestID=e.currentProps.testID),null!=e.currentProps.accessibilityLabel&&(r.currentPropsAccessLabel=e.currentProps.accessibilityLabel)),e.pendingProps&&(r.pendingPropsKeys=Object.keys(e.pendingProps).slice(0,15),null!=e.pendingProps.nativeID&&(r.pendingPropsNativeID=e.pendingProps.nativeID),null!=e.pendingProps.testID&&(r.pendingPropsTestID=e.pendingProps.testID))}if(n._debugOwner){const e=n._debugOwner;r.debugOwnerType=e.type?.name||e.type?.displayName||typeof e.type}n._debugSource&&(r.debugSource=n._debugSource)}return r}const DEBUG_LOGGING=!1;let renderingLock=!1,renderingLockTimeout=null;const RENDER_LOCK_DURATION=350,renderedOverlayTags=new Set;function isOurOverlayTag(e){return null!=e&&renderedOverlayTags.has(e)}const HIGHLIGHT_OVERLAY_COMPONENTS=new Set(["HighlightUpdatesOverlay","HighlightUpdatesModal","HighlightFilterView","RenderDetailView","RenderListItem","RenderListItemInner","RenderHistoryViewer","RenderHistoryFooter","RenderCauseBadge","TwoLevelCauseBadge","EnhancedCauseDisplay","IsolatedRenderList","IsolatedRenderListInner","StatsDisplay","StatsDisplayInner","CurrentStateView","EventStepperFooter","EventsModal","UnifiedEventList","UnifiedEventItem","UnifiedEventDetail","UnifiedEventFilters","UnifiedEventViewer","ReactQueryEventDetail","EventsCopySettingsView"]),HIGHLIGHT_OVERLAY_NATIVE_IDS=new Set(["highlight-updates-overlay","__rn_buoy__highlight-modal"]),HIGHLIGHT_OVERLAY_PREFIXES=["HighlightUpdates","RenderList","RenderDetail","RenderHistory","RenderCause","EventStepper","UnifiedEvent","EventsModal","EventsCopy"],OTHER_DEV_TOOLS_COMPONENTS=new Set(["JsModalComponent","JsModal","TypePicker","PatternInput","PatternChip","DetectedItemsSection","DetectedCategoryBadge","IdentifierBadge","CategoryBadge","AppRenderer","AppOverlay","FloatingTools","DialDevTools","DevToolsVisibilityProvider","AppHostProvider","MinimizedToolsProvider","ModalHeader","TabSelector","SectionHeader","DraggableHeader","WindowControls","TreeDiffViewer","DataViewer","SplitDiffViewer","VirtualizedDataExplorer","DiffSummary","TypeLegend","IndentGuides","IndentGuidesOverlay","CyberpunkInput","DiffView","AnswerCard","DetailsSection","DetailRow","QuickActionsSection","FilterOptionCard","SearchSection","SearchSectionInner","HeaderActions","HeaderActionsInner","MainListHeader","FilterViewHeader","DetailViewHeader","FloatingMenu","FloatingDevTools","MinimizedToolsStack","MinimizedToolsContext","GlitchToolButton","ExpandablePopover","CollapsedPeek","ExpandedWrapper","DialIcon","DialMenu","DialIconItem","OnboardingTooltip","MenuLauncherIcon","DevToolsSettingsModal","SettingsModal","ToolCard","ToolIcon","GripVerticalIcon","UserStatus","Divider","ChevronDown","ChevronUp","ChevronLeft","ChevronRight","ChevronDownIcon","ChevronUpIcon","ChevronLeftIcon","ChevronRightIcon","LogBox","LogBoxLog","LogBoxLogNotification","LogBoxNotificationContainer","_LogBoxNotificationContainer","LogBoxInspector","LogBoxInspectorContainer","LogBoxInspectorHeader","LogBoxInspectorBody","LogBoxInspectorFooter","LogBoxInspectorMessageHeader","LogBoxInspectorStackFrame","LogBoxInspectorSection","LogBoxButton","LogBoxMessage"]),OTHER_DEV_TOOLS_PREFIXES=["JsModal","TreeDiff","DataViewer","DiffView","DiffSummary","Virtualized","Floating","Minimized","Dial","Expandable","Chevron","Glitch","Settings","Onboarding","DevTools"],OTHER_DEV_TOOLS_NATIVE_IDS=new Set(["jsmodal-root","logbox_inspector","logbox"]),devToolsNodeCache=new Map,CACHE_MAX_SIZE=500;function clearDevToolsCache(){devToolsNodeCache.clear()}function isHighlightOverlayNativeID(e){return!(!e||!HIGHLIGHT_OVERLAY_NATIVE_IDS.has(e)&&!e.startsWith("__highlight_"))}function isOtherDevToolsNativeID(e){if(!e)return!1;if(OTHER_DEV_TOOLS_NATIVE_IDS.has(e))return!0;const n=e.charCodeAt(0);return!(95!==n||!e.startsWith("__rn_buoy__"))||!(108!==n||!e.startsWith("logbox"))}function getComponentName(e){if(!e)return null;const n=e.type;if(n){if("string"==typeof n)return n;if(n.name)return n.name;if(n.displayName)return n.displayName}const t=e.elementType;if(t){if(t.name)return t.name;if(t.displayName)return t.displayName}return null}const INTERNAL_COMPONENT_NAMES=new Set(["View","Text","TextImpl","Image","ScrollView","FlatList","SectionList","TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback","Pressable","TextInput","Switch","ActivityIndicator","Modal","StatusBar","KeyboardAvoidingView","AnimatedComponent","AnimatedComponentWrapper","ScreenContainer","ScreenStack","Screen","ScreenContentWrapper","Svg","G","Path","Rect","Circle","Line","Polygon","Polyline","Ellipse","Text as SVGText","TSpan","TextPath","Use","Symbol","Defs","ClipPath","LinearGradient","RadialGradient","Stop","Mask","Pattern","Image as SVGImage","SafeAreaProvider","SafeAreaView","SafeAreaListener","GestureHandlerRootView","GestureDetector","ReanimatedView","ReanimatedText","ReanimatedImage","ReanimatedScrollView","Fragment","Suspense","Provider","Consumer","Context","ForwardRef"]);function isInternalComponent(e){return!e||!!INTERNAL_COMPONENT_NAMES.has(e)||"Unknown"===e||"Component"===e||!!e.startsWith("Animated")}function getOwningComponentName(e){if(!e)return null;let n=e._debugOwner||e.return,t=0,r=null;for(;n&&t<30;){const e=getComponentName(n);if(e&&"string"!=typeof n.type&&(r||(r=e),!isInternalComponent(e)))return e;n=n.return,t++}return r}function isOurOverlayNode(e){const n=e,t=n?.canonical?.internalInstanceHandle||n?._internalInstanceHandle||n?._internalFiberInstanceHandleDEV,r=getNativeTag(e)||getNativeTag(n?.canonical?.publicInstance),i=_RenderTracker.RenderTracker.getSettings().excludeDevTools;if(null!=r&&i){const e=devToolsNodeCache.get(r);if(void 0!==e)return e}let a=!1;const o=t?.pendingProps?.nativeID||t?.memoizedProps?.nativeID||n?.canonical?.currentProps?.nativeID||null;if((isHighlightOverlayNativeID(o)||i&&isOtherDevToolsNativeID(o))&&(a=!0),!a&&t){let e=t,n=0;for(;e&&n<30;){const t=getComponentName(e);if(t){if(HIGHLIGHT_OVERLAY_COMPONENTS.has(t)){a=!0;break}for(const e of HIGHLIGHT_OVERLAY_PREFIXES)if(t.startsWith(e)){a=!0;break}if(a)break;if(i){if(OTHER_DEV_TOOLS_COMPONENTS.has(t)){a=!0;break}for(const e of OTHER_DEV_TOOLS_PREFIXES)if(t.startsWith(e)){a=!0;break}if(a)break}}const r=e.pendingProps?.nativeID||e.memoizedProps?.nativeID;if(isHighlightOverlayNativeID(r)){a=!0;break}if(i&&isOtherDevToolsNativeID(r)){a=!0;break}e=e.return,n++}}if(a&&null!=r&&i){if(devToolsNodeCache.size>=500){const e=Array.from(devToolsNodeCache.keys());for(let n=0;n<250;n++)devToolsNodeCache.delete(e[n])}devToolsNodeCache.set(r,a)}return a}function getColorForRenderCount(e){const n=Math.min(e-1,COLORS.length-1);return COLORS[Math.max(0,n)]}function handleTraceUpdates(e){if(0===e.size)return;if(!globalEnabled&&!backgroundTrackingEnabled)return;if(_RenderTracker.RenderTracker.getState().isPaused)return;_PerformanceLogger.PerformanceLogger.isEnabled()&&(0,_PerformanceLogger.markEventReceived)();const n=_RenderTracker.RenderTracker.getBatchSize(),t=_PerformanceLogger.PerformanceLogger.startBatch(e.size,n),r=[];let i=0,a=0;const o=_RenderTracker.RenderTracker.hasActiveFilters();for(const n of e)if(n&&"object"==typeof n)try{if(isOurOverlayNode(n)){i++;continue}const e=getPublicInstance(n),t=getNativeTag(n)||getNativeTag(e);if(null==t)continue;const s=extractComponentInfo(n);if(o&&!_RenderTracker.RenderTracker.passesFilters(s)){a++;continue}let l,c;_RenderTracker.RenderTracker.getSettings().showRenderCount?(l=(nodeRenderCounts.get(t)||0)+1,nodeRenderCounts.set(t,l),c=getColorForRenderCount(l)):(l=0,c=COLORS[0]),r.push({node:n,color:c,count:l})}catch{}if(t.markFilteringComplete(i+a,r.length),0===r.length)return void t.finish();const s=_RenderTracker.RenderTracker.getRenderEventCallbackCount()>0;if(!highlightCallback&&!s)return void(isProcessing=!1);t.markMeasurementStart();const l=r.slice(0,n).map(({node:e,color:n,count:t})=>new Promise(r=>{const i=getPublicInstance(e);if(!i)return void r({rect:null,stateNode:e,color:n,count:t});const a=getNativeTag(e)||getNativeTag(i);if(null!=a)try{i.measure((i,o,s,l,c,d)=>{r(null!=c&&null!=d&&null!=s&&null!=l?{rect:{id:a,x:c,y:d,width:s,height:l,color:n,count:t},stateNode:e,color:n,count:t}:{rect:null,stateNode:e,color:n,count:t})})}catch(i){r({rect:null,stateNode:e,color:n,count:t})}else r({rect:null,stateNode:e,color:n,count:t})}));Promise.all(l).then(e=>{const n=e.filter(e=>null!==e.rect),r=n.map(e=>e.rect);t.markMeasurementComplete(n.length,e.length-n.length);const i=_RenderTracker.RenderTracker.getSettings(),a=i.trackRenderCauses&&i.showRenderCount;let o=null;if(a){o=new Set;for(const{rect:e}of n)e&&o.add(e.id)}if(globalEnabled){_RenderTracker.RenderTracker.startBatch();for(const{rect:e,stateNode:t,color:r,count:s}of n)if(e){const n=extractComponentInfo(t);let l;if(a&&o){const n=t,r=n?.canonical?.internalInstanceHandle;l=(0,_RenderCauseDetector.detectRenderCause)(e.id,r,o,i.debugLogLevel)}_RenderTracker.RenderTracker.trackRender({nativeTag:e.id,viewType:n.viewType,testID:n.testID,nativeID:n.nativeID,accessibilityLabel:n.accessibilityLabel,componentName:n.componentName,measurements:{x:e.x,y:e.y,width:e.width,height:e.height},color:r,count:s,renderCause:l})}_RenderTracker.RenderTracker.endBatch()}else for(const{rect:e,stateNode:t,color:r,count:s}of n)if(e){const n=extractComponentInfo(t);let l;if(a&&o){const n=t,r=n?.canonical?.internalInstanceHandle;l=(0,_RenderCauseDetector.detectRenderCause)(e.id,r,o,i.debugLogLevel)}_RenderTracker.RenderTracker.emitRenderEvent({nativeTag:e.id,viewType:n.viewType,testID:n.testID,nativeID:n.nativeID,accessibilityLabel:n.accessibilityLabel,componentName:n.componentName,measurements:{x:e.x,y:e.y,width:e.width,height:e.height},color:r,count:s,renderCause:l})}t.markTrackingComplete(),r.length>0&&highlightCallback&&globalEnabled&&highlightCallback(r),t.markCallbackComplete(),t.finish()}).catch(e=>{console.error("[HighlightUpdates] Error in measurement pipeline:",e),t.finish()})}function setTraceUpdatesOnRenderers(e){if(!hook?.rendererInterfaces)return void debugLog("No rendererInterfaces available");let n=0;if(hook.rendererInterfaces.forEach((t,r)=>{if("function"==typeof t.setTraceUpdatesEnabled)try{t.setTraceUpdatesEnabled(e),n++,debugLog(`Renderer ${r}: setTraceUpdatesEnabled(${e})`)}catch(e){debugLog(`Renderer ${r}: error setting trace updates`,e)}else debugLog(`Renderer ${r}: no setTraceUpdatesEnabled method`)}),debugLog(`Set trace updates ${e?"enabled":"disabled"} on ${n} renderer(s)`),hook.reactDevtoolsAgent?.setTraceUpdatesEnabled)try{hook.reactDevtoolsAgent.setTraceUpdatesEnabled(e),debugLog("Also set trace updates on agent")}catch(e){debugLog("Error setting trace updates on agent",e)}}function subscribeToTraceUpdates(){hook&&(traceUpdatesUnsubscribe||(debugLog("Subscribing to traceUpdates event"),"function"==typeof hook.sub?(traceUpdatesUnsubscribe=hook.sub("traceUpdates",handleTraceUpdates),debugLog("Subscribed using hook.sub()")):debugLog("hook.sub not available, traceUpdates may not work"),setTraceUpdatesOnRenderers(!0)))}function unsubscribeFromTraceUpdates(){setTraceUpdatesOnRenderers(!1),traceUpdatesUnsubscribe&&(traceUpdatesUnsubscribe(),traceUpdatesUnsubscribe=null,debugLog("Unsubscribed from traceUpdates event"))}function setHighlightCallback(e){highlightCallback=e,debugLog("Highlight callback "+(e?"set":"cleared"))}function setBadgePressCallback(e){badgePressCallback=e,debugLog("Badge press callback "+(e?"set":"cleared"))}function getBadgePressCallback(){return badgePressCallback}function handleBadgePress(e){badgePressCallback&&badgePressCallback(e)}let spotlightCallback=null,currentSpotlightTag=null;function setSpotlightCallback(e){spotlightCallback=e,e&&null!==currentSpotlightTag&&e(currentSpotlightTag)}function setSpotlight(e){currentSpotlightTag=e,spotlightCallback&&spotlightCallback(e)}function getSpotlight(){return currentSpotlightTag}function notifyStateListeners(){stateListeners.forEach(e=>{try{e(globalEnabled)}catch(e){console.error("[HighlightUpdates] Error in state listener:",e)}})}function subscribe(e){return stateListeners.add(e),e(globalEnabled),()=>{stateListeners.delete(e)}}function initialize(){if("undefined"!=typeof __DEV__&&!__DEV__)return debugLog("Only available in development builds"),!1;if(initialized)return!0;if(hook=window?.__REACT_DEVTOOLS_GLOBAL_HOOK__||null,!hook)return debugLog("React DevTools hook not found"),!1;if(debugLog("Hook found"),debugLog(`Hook has sub: ${"function"==typeof hook.sub}`),debugLog(`Hook has on: ${"function"==typeof hook.on}`),debugLog(`Hook has emit: ${"function"==typeof hook.emit}`),(0,_ProfilerInterceptor.installProfilerInterceptor)(),hook.rendererInterfaces&&hook.rendererInterfaces.size>0)return initialized=!0,debugLog(`Initialized with ${hook.rendererInterfaces.size} renderer(s)`),exposeGlobally(),!0;const e=setInterval(()=>{hook?.rendererInterfaces&&hook.rendererInterfaces.size>0&&(clearInterval(e),initialized=!0,debugLog(`Initialized with ${hook.rendererInterfaces.size} renderer(s) (delayed)`),exposeGlobally())},100);return setTimeout(()=>clearInterval(e),1e4),!1}function exposeGlobally(){"undefined"!=typeof window&&(window.__HIGHLIGHT_UPDATES_CONTROLLER__={enable:enable,disable:disable,toggle:toggle,isEnabled:isEnabled,setEnabled:setEnabled,subscribe:subscribe,initialize:initialize,destroy:destroy,isInitialized:()=>initialized,setHighlightCallback:setHighlightCallback})}function enable(){("undefined"==typeof __DEV__||__DEV__)&&(initialized||initialize(),globalEnabled||(debugLog("Enabling highlights"),backgroundTrackingEnabled||subscribeToTraceUpdates(),_RenderTracker.RenderTracker.start(),globalEnabled=!0,notifyStateListeners(),(0,_sharedUi.notifySubscriberCountChange)("render")))}function disable(){("undefined"==typeof __DEV__||__DEV__)&&globalEnabled&&(debugLog("Disabling highlights"),renderingLock=!1,isProcessing=!1,renderingLockTimeout&&(clearTimeout(renderingLockTimeout),renderingLockTimeout=null),renderedOverlayTags.clear(),devToolsNodeCache.clear(),(0,_RenderCauseDetector.clearRenderCauseState)(),backgroundTrackingEnabled||unsubscribeFromTraceUpdates(),_RenderTracker.RenderTracker.stop(),globalEnabled=!1,notifyStateListeners(),(0,_sharedUi.notifySubscriberCountChange)("render"))}function enableBackgroundTracking(){("undefined"==typeof __DEV__||__DEV__)&&(backgroundTrackingEnabled||(initialized||initialize(),debugLog("Enabling background tracking"),globalEnabled||subscribeToTraceUpdates(),backgroundTrackingEnabled=!0))}function disableBackgroundTracking(){("undefined"==typeof __DEV__||__DEV__)&&backgroundTrackingEnabled&&(debugLog("Disabling background tracking"),backgroundTrackingEnabled=!1,globalEnabled||unsubscribeFromTraceUpdates())}function isBackgroundTrackingEnabled(){return backgroundTrackingEnabled}function toggle(){("undefined"==typeof __DEV__||__DEV__)&&(globalEnabled?disable():enable())}function clearRenderCounts(){nodeRenderCounts.clear(),_RenderTracker.RenderTracker.clear(),debugLog("Cleared render counts")}function isEnabled(){return globalEnabled}function setEnabled(e){e?enable():disable()}function isInitialized(){return initialized}function notifyFreezeListeners(){freezeListeners.forEach(e=>{try{e(isFrozen)}catch(e){console.error("[HighlightUpdates] Error in freeze listener:",e)}})}function subscribeToFreeze(e){return freezeListeners.add(e),e(isFrozen),()=>{freezeListeners.delete(e)}}function freeze(){("undefined"==typeof __DEV__||__DEV__)&&(isFrozen||(isFrozen=!0,debugLog("Freeze mode enabled"),notifyFreezeListeners()))}function unfreeze(){("undefined"==typeof __DEV__||__DEV__)&&isFrozen&&(isFrozen=!1,debugLog("Freeze mode disabled"),highlightCallback&&highlightCallback([]),notifyFreezeListeners())}function toggleFreeze(){isFrozen?unfreeze():freeze()}function getFrozen(){return isFrozen}function destroy(){initialized&&(unsubscribeFromTraceUpdates(),(0,_ProfilerInterceptor.uninstallProfilerInterceptor)(),(0,_ProfilerInterceptor.setComparisonCallback)(null),isFrozen=!1,freezeListeners.clear(),globalEnabled=!1,hook=null,highlightCallback=null,initialized=!1,"undefined"!=typeof window&&delete window.__HIGHLIGHT_UPDATES_CONTROLLER__,debugLog("Destroyed"))}const HighlightUpdatesController={subscribe:subscribe,enable:enable,disable:disable,toggle:toggle,isEnabled:isEnabled,setEnabled:setEnabled,initialize:initialize,destroy:destroy,isInitialized:isInitialized,setHighlightCallback:setHighlightCallback,clearRenderCounts:clearRenderCounts,freeze:freeze,unfreeze:unfreeze,toggleFreeze:toggleFreeze,getFrozen:getFrozen,subscribeToFreeze:subscribeToFreeze,setBadgePressCallback:setBadgePressCallback,getBadgePressCallback:getBadgePressCallback,handleBadgePress:handleBadgePress,setSpotlightCallback:setSpotlightCallback,setSpotlight:setSpotlight,getSpotlight:getSpotlight,clearDevToolsCache:clearDevToolsCache,enableBackgroundTracking:enableBackgroundTracking,disableBackgroundTracking:disableBackgroundTracking,isBackgroundTrackingEnabled:isBackgroundTrackingEnabled};var _default=exports.default=HighlightUpdatesController;
|
|
1
|
+
/**
|
|
2
|
+
* Highlight Updates Controller
|
|
3
|
+
*
|
|
4
|
+
* Standalone implementation that replicates React DevTools' "Highlight updates
|
|
5
|
+
* when components render" feature WITHOUT requiring DevTools to be connected.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* This controller directly enables the DevTools backend's traceUpdates feature
|
|
9
|
+
* by calling setTraceUpdatesEnabled(true) on each renderer interface. This is
|
|
10
|
+
* the same function that DevTools' frontend calls when you check the
|
|
11
|
+
* "Highlight updates when components render" checkbox.
|
|
12
|
+
*
|
|
13
|
+
* Key insight: The rendererInterfaces are available on the global hook even
|
|
14
|
+
* without DevTools frontend connected. We just need to enable tracing directly.
|
|
15
|
+
*
|
|
16
|
+
* Flow:
|
|
17
|
+
* 1. User enables highlights via toggle()
|
|
18
|
+
* 2. We call rendererInterface.setTraceUpdatesEnabled(true) on all renderers
|
|
19
|
+
* 3. DevTools backend now tracks renders and emits 'traceUpdates' events
|
|
20
|
+
* 4. We subscribe to 'traceUpdates' via hook.sub()
|
|
21
|
+
* 5. When components re-render, we receive the Set of host stateNodes
|
|
22
|
+
* 6. We measure each node and render colored border highlights
|
|
23
|
+
*
|
|
24
|
+
* This gives us 100% accuracy with DevTools behavior because we're using the
|
|
25
|
+
* exact same detection code - we're just enabling it programmatically.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
"use strict";
|
|
29
|
+
|
|
30
|
+
Object.defineProperty(exports, "__esModule", {
|
|
31
|
+
value: true
|
|
32
|
+
});
|
|
33
|
+
exports.default = void 0;
|
|
34
|
+
var _ProfilerInterceptor = require("./ProfilerInterceptor");
|
|
35
|
+
var _RenderTracker = require("./RenderTracker");
|
|
36
|
+
var _PerformanceLogger = require("./PerformanceLogger");
|
|
37
|
+
var _RenderCauseDetector = require("./RenderCauseDetector");
|
|
38
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
39
|
+
// State
|
|
40
|
+
let globalEnabled = false; // User-controlled toggle for visual highlights
|
|
41
|
+
let backgroundTrackingEnabled = false; // Auto-enabled when event callbacks exist (Events tool)
|
|
42
|
+
let initialized = false;
|
|
43
|
+
let hook = null;
|
|
44
|
+
let highlightCallback = null;
|
|
45
|
+
let badgePressCallback = null;
|
|
46
|
+
let traceUpdatesUnsubscribe = null;
|
|
47
|
+
let isProcessing = false;
|
|
48
|
+
|
|
49
|
+
// Freeze frame state
|
|
50
|
+
let isFrozen = false;
|
|
51
|
+
const freezeListeners = new Set();
|
|
52
|
+
const stateListeners = new Set();
|
|
53
|
+
|
|
54
|
+
// Track render counts per node (for color assignment like DevTools)
|
|
55
|
+
// Map from nativeTag to render count - nativeTag is stable across re-renders
|
|
56
|
+
const nodeRenderCounts = new Map();
|
|
57
|
+
|
|
58
|
+
// Color palette for highlights (same as DevTools)
|
|
59
|
+
// From: react-devtools-core/dist/backend.js line 6361
|
|
60
|
+
const COLORS = ["#37afa9", "#63b19e", "#80b393", "#97b488", "#abb67d", "#beb771", "#cfb965", "#dfba57", "#efbb49", "#febc38"];
|
|
61
|
+
|
|
62
|
+
// Set to true for verbose debugging of our implementation
|
|
63
|
+
// Disabled for now to focus on profiler logs only
|
|
64
|
+
const DEBUG = false;
|
|
65
|
+
function debugLog(message, data) {
|
|
66
|
+
if (!DEBUG) return;
|
|
67
|
+
if (data !== undefined) {
|
|
68
|
+
console.log(`[HighlightUpdates] ${message}`, data);
|
|
69
|
+
} else {
|
|
70
|
+
console.log(`[HighlightUpdates] ${message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the public instance from a stateNode (the node passed in traceUpdates)
|
|
76
|
+
*/
|
|
77
|
+
function getPublicInstance(stateNode) {
|
|
78
|
+
if (!stateNode) return null;
|
|
79
|
+
const node = stateNode;
|
|
80
|
+
|
|
81
|
+
// Fabric: stateNode.canonical.publicInstance
|
|
82
|
+
if (node.canonical?.publicInstance) {
|
|
83
|
+
return node.canonical.publicInstance;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Legacy Fabric: stateNode.canonical with measure
|
|
87
|
+
if (node.canonical && typeof node.canonical.measure === "function") {
|
|
88
|
+
return node.canonical;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Legacy renderer: stateNode has measure directly
|
|
92
|
+
if (typeof node.measure === "function") {
|
|
93
|
+
return node;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get native tag from public instance or stateNode
|
|
100
|
+
*/
|
|
101
|
+
function getNativeTag(instance) {
|
|
102
|
+
if (instance == null) return null;
|
|
103
|
+
const inst = instance;
|
|
104
|
+
|
|
105
|
+
// Try direct properties
|
|
106
|
+
if (inst.__nativeTag != null) return inst.__nativeTag;
|
|
107
|
+
if (inst._nativeTag != null) return inst._nativeTag;
|
|
108
|
+
if (inst.nativeTag != null) return inst.nativeTag;
|
|
109
|
+
|
|
110
|
+
// Try canonical
|
|
111
|
+
if (inst.canonical) {
|
|
112
|
+
if (inst.canonical.__nativeTag != null) return inst.canonical.__nativeTag;
|
|
113
|
+
if (inst.canonical._nativeTag != null) return inst.canonical._nativeTag;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Extract component info from a stateNode for RenderTracker
|
|
120
|
+
*/
|
|
121
|
+
function extractComponentInfo(stateNode) {
|
|
122
|
+
const node = stateNode;
|
|
123
|
+
const info = {
|
|
124
|
+
viewType: "Unknown"
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Get viewType from viewConfig
|
|
128
|
+
if (node?.canonical?.viewConfig?.uiViewClassName) {
|
|
129
|
+
info.viewType = node.canonical.viewConfig.uiViewClassName;
|
|
130
|
+
} else if (node?.viewConfig?.uiViewClassName) {
|
|
131
|
+
info.viewType = node.viewConfig.uiViewClassName;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Try to get props from various locations
|
|
135
|
+
// Fabric: canonical.internalInstanceHandle, Paper: _internalInstanceHandle, RN CLI DEV: _internalFiberInstanceHandleDEV
|
|
136
|
+
const fiber = node?.canonical?.internalInstanceHandle || node?._internalInstanceHandle || node?._internalFiberInstanceHandleDEV;
|
|
137
|
+
const currentProps = node?.canonical?.currentProps;
|
|
138
|
+
const pendingProps = node?.canonical?.pendingProps;
|
|
139
|
+
const fiberPendingProps = fiber?.pendingProps;
|
|
140
|
+
const fiberMemoizedProps = fiber?.memoizedProps;
|
|
141
|
+
|
|
142
|
+
// Extract testID
|
|
143
|
+
info.testID = currentProps?.testID || pendingProps?.testID || fiberPendingProps?.testID || fiberMemoizedProps?.testID || undefined;
|
|
144
|
+
|
|
145
|
+
// Extract nativeID
|
|
146
|
+
info.nativeID = currentProps?.nativeID || pendingProps?.nativeID || fiberPendingProps?.nativeID || fiberMemoizedProps?.nativeID || undefined;
|
|
147
|
+
|
|
148
|
+
// Extract accessibilityLabel
|
|
149
|
+
info.accessibilityLabel = currentProps?.accessibilityLabel || pendingProps?.accessibilityLabel || fiberPendingProps?.accessibilityLabel || fiberMemoizedProps?.accessibilityLabel || undefined;
|
|
150
|
+
|
|
151
|
+
// Extract componentName - use getOwningComponentName to get the React component
|
|
152
|
+
// that rendered this host component
|
|
153
|
+
info.componentName = getOwningComponentName(fiber) || undefined;
|
|
154
|
+
return info;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Describe a node for logging (extensive version for debugging)
|
|
159
|
+
*/
|
|
160
|
+
function describeNodeForLog(stateNode) {
|
|
161
|
+
const node = stateNode;
|
|
162
|
+
const publicInstance = getPublicInstance(stateNode);
|
|
163
|
+
const nativeTag = getNativeTag(stateNode) || getNativeTag(publicInstance);
|
|
164
|
+
const info = {
|
|
165
|
+
nativeTag: nativeTag ?? "unknown",
|
|
166
|
+
type: node?.canonical ? "Fabric" : "Legacy",
|
|
167
|
+
hasMeasure: typeof publicInstance?.measure === "function"
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Extract as much info as possible from the stateNode
|
|
171
|
+
if (node) {
|
|
172
|
+
// Direct properties on stateNode
|
|
173
|
+
info.stateNodeKeys = Object.keys(node).slice(0, 20);
|
|
174
|
+
|
|
175
|
+
// Check the 'node' property (might have useful info)
|
|
176
|
+
if (node.node) {
|
|
177
|
+
info.nodeKeys = Object.keys(node.node).slice(0, 20);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check for viewConfig (contains component type info)
|
|
181
|
+
if (node.viewConfig) {
|
|
182
|
+
info.viewConfig = {
|
|
183
|
+
uiViewClassName: node.viewConfig.uiViewClassName,
|
|
184
|
+
validAttributes: node.viewConfig.validAttributes ? Object.keys(node.viewConfig.validAttributes).slice(0, 10) : undefined
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check canonical structure (Fabric)
|
|
189
|
+
if (node.canonical) {
|
|
190
|
+
const canonical = node.canonical;
|
|
191
|
+
info.canonicalKeys = Object.keys(canonical).slice(0, 20);
|
|
192
|
+
if (canonical.viewConfig) {
|
|
193
|
+
info.canonicalViewConfig = {
|
|
194
|
+
uiViewClassName: canonical.viewConfig.uiViewClassName
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (canonical.nativeTag != null) {
|
|
198
|
+
info.canonicalNativeTag = canonical.nativeTag;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check internalInstanceHandle (THE FIBER!)
|
|
202
|
+
if (canonical.internalInstanceHandle) {
|
|
203
|
+
const fiber = canonical.internalInstanceHandle;
|
|
204
|
+
info.fiberKeys = Object.keys(fiber).slice(0, 30);
|
|
205
|
+
|
|
206
|
+
// Component type info
|
|
207
|
+
if (fiber.type) {
|
|
208
|
+
info.fiberType = typeof fiber.type === "function" ? fiber.type.name || fiber.type.displayName || "function" : fiber.type;
|
|
209
|
+
}
|
|
210
|
+
if (fiber.elementType) {
|
|
211
|
+
info.fiberElementType = typeof fiber.elementType === "function" ? fiber.elementType.name || fiber.elementType.displayName || "function" : fiber.elementType;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Debug info on fiber
|
|
215
|
+
if (fiber._debugOwner) {
|
|
216
|
+
const owner = fiber._debugOwner;
|
|
217
|
+
info.fiberDebugOwner = owner.type?.name || owner.type?.displayName || owner.elementType?.name || "unknown";
|
|
218
|
+
}
|
|
219
|
+
if (fiber._debugSource) {
|
|
220
|
+
info.fiberDebugSource = fiber._debugSource;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Fiber tag (tells us what kind of fiber it is)
|
|
224
|
+
if (fiber.tag != null) {
|
|
225
|
+
info.fiberTag = fiber.tag;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// pendingProps on fiber might have testID/nativeID
|
|
229
|
+
if (fiber.pendingProps) {
|
|
230
|
+
if (fiber.pendingProps.nativeID) info.fiberPendingNativeID = fiber.pendingProps.nativeID;
|
|
231
|
+
if (fiber.pendingProps.testID) info.fiberPendingTestID = fiber.pendingProps.testID;
|
|
232
|
+
}
|
|
233
|
+
if (fiber.memoizedProps) {
|
|
234
|
+
if (fiber.memoizedProps.nativeID) info.fiberMemoizedNativeID = fiber.memoizedProps.nativeID;
|
|
235
|
+
if (fiber.memoizedProps.testID) info.fiberMemoizedTestID = fiber.memoizedProps.testID;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check publicInstance
|
|
240
|
+
if (canonical.publicInstance) {
|
|
241
|
+
const pub = canonical.publicInstance;
|
|
242
|
+
info.publicInstanceKeys = Object.keys(pub).slice(0, 20);
|
|
243
|
+
|
|
244
|
+
// Look for nativeID
|
|
245
|
+
if (pub.nativeID != null) info.nativeID = pub.nativeID;
|
|
246
|
+
if (pub._nativeID != null) info._nativeID = pub._nativeID;
|
|
247
|
+
|
|
248
|
+
// Look for props
|
|
249
|
+
if (pub.props) {
|
|
250
|
+
info.publicInstanceProps = Object.keys(pub.props).slice(0, 15);
|
|
251
|
+
if (pub.props.nativeID) info.propsNativeID = pub.props.nativeID;
|
|
252
|
+
if (pub.props.testID) info.propsTestID = pub.props.testID;
|
|
253
|
+
if (pub.props.accessibilityLabel) info.accessibilityLabel = pub.props.accessibilityLabel;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check for currentProps - log actual VALUES
|
|
258
|
+
if (canonical.currentProps) {
|
|
259
|
+
info.currentPropsKeys = Object.keys(canonical.currentProps).slice(0, 15);
|
|
260
|
+
// Log actual values of identifying props
|
|
261
|
+
if (canonical.currentProps.nativeID != null) info.currentPropsNativeID = canonical.currentProps.nativeID;
|
|
262
|
+
if (canonical.currentProps.testID != null) info.currentPropsTestID = canonical.currentProps.testID;
|
|
263
|
+
if (canonical.currentProps.accessibilityLabel != null) info.currentPropsAccessLabel = canonical.currentProps.accessibilityLabel;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Check for pendingProps
|
|
267
|
+
if (canonical.pendingProps) {
|
|
268
|
+
info.pendingPropsKeys = Object.keys(canonical.pendingProps).slice(0, 15);
|
|
269
|
+
if (canonical.pendingProps.nativeID != null) info.pendingPropsNativeID = canonical.pendingProps.nativeID;
|
|
270
|
+
if (canonical.pendingProps.testID != null) info.pendingPropsTestID = canonical.pendingProps.testID;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check for fiber reference directly on node
|
|
275
|
+
if (node._debugOwner) {
|
|
276
|
+
const owner = node._debugOwner;
|
|
277
|
+
info.debugOwnerType = owner.type?.name || owner.type?.displayName || typeof owner.type;
|
|
278
|
+
}
|
|
279
|
+
if (node._debugSource) {
|
|
280
|
+
info.debugSource = node._debugSource;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return info;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Set to true to enable debug logging
|
|
287
|
+
const DEBUG_LOGGING = false;
|
|
288
|
+
|
|
289
|
+
// Lock to prevent infinite loops when rendering our overlay
|
|
290
|
+
let renderingLock = false;
|
|
291
|
+
let renderingLockTimeout = null;
|
|
292
|
+
|
|
293
|
+
// How long to ignore new events after starting a render (ms)
|
|
294
|
+
// Should match HIGHLIGHT_DURATION in the overlay to prevent false triggers
|
|
295
|
+
const RENDER_LOCK_DURATION = 350;
|
|
296
|
+
|
|
297
|
+
// Track nativeTags of components we're currently highlighting
|
|
298
|
+
// This helps us identify (and skip) our own overlay Views if they re-render
|
|
299
|
+
const renderedOverlayTags = new Set();
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Check if a nativeTag belongs to our overlay (was rendered by us previously)
|
|
303
|
+
*/
|
|
304
|
+
function isOurOverlayTag(nativeTag) {
|
|
305
|
+
if (nativeTag == null) return false;
|
|
306
|
+
return renderedOverlayTags.has(nativeTag);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// OPTIMIZED DEV TOOLS DETECTION
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// Performance optimizations applied:
|
|
313
|
+
// 1. Result caching by nativeTag (same tag = same result)
|
|
314
|
+
// 2. Set-based O(1) lookups instead of multiple string comparisons
|
|
315
|
+
// 3. Component name check FIRST (appears early in tree, ~depth 5-8)
|
|
316
|
+
// 4. Early exit on first match
|
|
317
|
+
// 5. Cached property access to avoid repeated traversal
|
|
318
|
+
// ============================================================================
|
|
319
|
+
|
|
320
|
+
// Components that MUST always be excluded to prevent infinite loops.
|
|
321
|
+
// Includes the Highlight Updates overlay AND the Events tool (which subscribes
|
|
322
|
+
// to render callbacks via RenderTracker.onRenderEvent - tracking its renders
|
|
323
|
+
// would create an infinite callback loop).
|
|
324
|
+
const HIGHLIGHT_OVERLAY_COMPONENTS = new Set([
|
|
325
|
+
// Highlight Updates overlay components
|
|
326
|
+
"HighlightUpdatesOverlay", "HighlightUpdatesModal", "HighlightFilterView", "RenderDetailView", "RenderListItem", "RenderListItemInner", "RenderHistoryViewer", "RenderHistoryFooter", "RenderCauseBadge", "TwoLevelCauseBadge", "EnhancedCauseDisplay", "IsolatedRenderList", "IsolatedRenderListInner", "StatsDisplay", "StatsDisplayInner", "CurrentStateView", "EventStepperFooter",
|
|
327
|
+
// Events tool components - subscribes to onRenderEvent, tracking these causes infinite loops
|
|
328
|
+
"EventsModal", "UnifiedEventList", "UnifiedEventItem", "UnifiedEventDetail", "UnifiedEventFilters", "UnifiedEventViewer", "ReactQueryEventDetail", "EventsCopySettingsView"]);
|
|
329
|
+
|
|
330
|
+
// Native IDs that MUST always be excluded (highlight overlay)
|
|
331
|
+
const HIGHLIGHT_OVERLAY_NATIVE_IDS = new Set(["highlight-updates-overlay", "__rn_buoy__highlight-modal"]);
|
|
332
|
+
|
|
333
|
+
// Prefixes for always-excluded components (highlight overlay + events tool)
|
|
334
|
+
const HIGHLIGHT_OVERLAY_PREFIXES = ["HighlightUpdates", "RenderList", "RenderDetail", "RenderHistory", "RenderCause", "EventStepper",
|
|
335
|
+
// Events tool - subscribes to onRenderEvent, must always be excluded
|
|
336
|
+
"UnifiedEvent", "EventsModal", "EventsCopy"];
|
|
337
|
+
|
|
338
|
+
// Other dev tools components - can be toggled via excludeDevTools setting
|
|
339
|
+
const OTHER_DEV_TOOLS_COMPONENTS = new Set([
|
|
340
|
+
// React Buoy devtools components
|
|
341
|
+
"JsModalComponent", "JsModal", "TypePicker", "PatternInput", "PatternChip", "DetectedItemsSection", "DetectedCategoryBadge", "IdentifierBadge", "CategoryBadge", "AppRenderer", "AppOverlay", "FloatingTools", "DialDevTools", "DevToolsVisibilityProvider", "AppHostProvider", "MinimizedToolsProvider",
|
|
342
|
+
// Shared UI components used in modals
|
|
343
|
+
"ModalHeader", "TabSelector", "SectionHeader", "DraggableHeader", "WindowControls",
|
|
344
|
+
// Shared UI data viewer components (JSON diff viewers, etc.)
|
|
345
|
+
"TreeDiffViewer", "DataViewer", "SplitDiffViewer", "VirtualizedDataExplorer", "DiffSummary", "TypeLegend", "IndentGuides", "IndentGuidesOverlay", "CyberpunkInput", "DiffView", "AnswerCard", "DetailsSection", "DetailRow", "QuickActionsSection", "FilterOptionCard",
|
|
346
|
+
// Modal header sub-components
|
|
347
|
+
"SearchSection", "SearchSectionInner", "HeaderActions", "HeaderActionsInner", "MainListHeader", "FilterViewHeader", "DetailViewHeader",
|
|
348
|
+
// Floating menu components (minimized tools, dial, etc.)
|
|
349
|
+
"FloatingMenu", "FloatingDevTools", "MinimizedToolsStack", "MinimizedToolsContext", "GlitchToolButton", "ExpandablePopover", "CollapsedPeek", "ExpandedWrapper", "DialIcon", "DialMenu", "DialIconItem", "OnboardingTooltip", "MenuLauncherIcon", "DevToolsSettingsModal", "SettingsModal",
|
|
350
|
+
// Image Overlay components
|
|
351
|
+
"ImageOverlayModal", "ImageOverlayStandalone", "ToolCard", "ToolIcon", "GripVerticalIcon", "UserStatus", "Divider",
|
|
352
|
+
// Shared UI icon components (Chevrons, etc.)
|
|
353
|
+
"ChevronDown", "ChevronUp", "ChevronLeft", "ChevronRight", "ChevronDownIcon", "ChevronUpIcon", "ChevronLeftIcon", "ChevronRightIcon",
|
|
354
|
+
// React Native LogBox components (shown on reload/errors)
|
|
355
|
+
"LogBox", "LogBoxLog", "LogBoxLogNotification", "LogBoxNotificationContainer", "_LogBoxNotificationContainer", "LogBoxInspector", "LogBoxInspectorContainer", "LogBoxInspectorHeader", "LogBoxInspectorBody", "LogBoxInspectorFooter", "LogBoxInspectorMessageHeader", "LogBoxInspectorStackFrame", "LogBoxInspectorSection", "LogBoxButton", "LogBoxMessage"]);
|
|
356
|
+
|
|
357
|
+
// Other dev tools prefixes - can be toggled via excludeDevTools setting
|
|
358
|
+
const OTHER_DEV_TOOLS_PREFIXES = ["JsModal", "TreeDiff", "DataViewer", "DiffView", "DiffSummary", "Virtualized",
|
|
359
|
+
// Floating menu prefixes
|
|
360
|
+
"Floating", "Minimized", "Dial", "Expandable", "Chevron", "Glitch", "Settings", "Onboarding", "DevTools", "ImageOverlay"];
|
|
361
|
+
|
|
362
|
+
// Other dev tools native IDs - can be toggled
|
|
363
|
+
const OTHER_DEV_TOOLS_NATIVE_IDS = new Set(["jsmodal-root", "image-overlay-standalone",
|
|
364
|
+
// LogBox native IDs
|
|
365
|
+
"logbox_inspector", "logbox"]);
|
|
366
|
+
|
|
367
|
+
// Cache: nativeTag -> isDevTools (avoid re-walking tree for same node)
|
|
368
|
+
const devToolsNodeCache = new Map();
|
|
369
|
+
const CACHE_MAX_SIZE = 500;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Clear the devtools node cache.
|
|
373
|
+
* Call this when the excludeDevTools setting changes to re-evaluate all nodes.
|
|
374
|
+
*/
|
|
375
|
+
function clearDevToolsCache() {
|
|
376
|
+
devToolsNodeCache.clear();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Check if a nativeID belongs to highlight overlay (always excluded)
|
|
381
|
+
*/
|
|
382
|
+
function isHighlightOverlayNativeID(nativeID) {
|
|
383
|
+
if (!nativeID) return false;
|
|
384
|
+
// Direct Set lookup (O(1))
|
|
385
|
+
if (HIGHLIGHT_OVERLAY_NATIVE_IDS.has(nativeID)) return true;
|
|
386
|
+
// Prefix check for highlight overlay
|
|
387
|
+
if (nativeID.startsWith("__highlight_")) return true;
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Check if a nativeID belongs to other dev tools (optionally excluded)
|
|
393
|
+
*/
|
|
394
|
+
function isOtherDevToolsNativeID(nativeID) {
|
|
395
|
+
if (!nativeID) return false;
|
|
396
|
+
// Direct Set lookup (O(1))
|
|
397
|
+
if (OTHER_DEV_TOOLS_NATIVE_IDS.has(nativeID)) return true;
|
|
398
|
+
// Prefix checks for other devtools nativeIDs
|
|
399
|
+
const firstChar = nativeID.charCodeAt(0);
|
|
400
|
+
if (firstChar === 95) {
|
|
401
|
+
// '_' = 95
|
|
402
|
+
if (nativeID.startsWith("__rn_buoy__")) return true;
|
|
403
|
+
}
|
|
404
|
+
// Check for LogBox nativeIDs (start with 'l')
|
|
405
|
+
if (firstChar === 108 && nativeID.startsWith("logbox")) {
|
|
406
|
+
// 'l' = 108
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get component name from fiber (cached property access pattern)
|
|
414
|
+
* For host components (RCTView, RCTText), returns the native type.
|
|
415
|
+
* Use getOwningComponentName to get the React component that rendered it.
|
|
416
|
+
*/
|
|
417
|
+
function getComponentName(fiber) {
|
|
418
|
+
if (!fiber) return null;
|
|
419
|
+
// Most common: type.name
|
|
420
|
+
const type = fiber.type;
|
|
421
|
+
if (type) {
|
|
422
|
+
if (typeof type === 'string') return type;
|
|
423
|
+
if (type.name) return type.name;
|
|
424
|
+
if (type.displayName) return type.displayName;
|
|
425
|
+
}
|
|
426
|
+
// Fallback: elementType
|
|
427
|
+
const elementType = fiber.elementType;
|
|
428
|
+
if (elementType) {
|
|
429
|
+
if (elementType.name) return elementType.name;
|
|
430
|
+
if (elementType.displayName) return elementType.displayName;
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Common React Native internal component names to skip when finding user components
|
|
436
|
+
const INTERNAL_COMPONENT_NAMES = new Set([
|
|
437
|
+
// React Native core
|
|
438
|
+
'View', 'Text', 'TextImpl', 'Image', 'ScrollView', 'FlatList', 'SectionList', 'TouchableOpacity', 'TouchableHighlight', 'TouchableWithoutFeedback', 'Pressable', 'TextInput', 'Switch', 'ActivityIndicator', 'Modal', 'StatusBar', 'KeyboardAvoidingView',
|
|
439
|
+
// Animated components
|
|
440
|
+
'AnimatedComponent', 'AnimatedComponentWrapper',
|
|
441
|
+
// React Navigation / Screens
|
|
442
|
+
'ScreenContainer', 'ScreenStack', 'Screen', 'ScreenContentWrapper',
|
|
443
|
+
// SVG components
|
|
444
|
+
'Svg', 'G', 'Path', 'Rect', 'Circle', 'Line', 'Polygon', 'Polyline', 'Ellipse', 'Text as SVGText', 'TSpan', 'TextPath', 'Use', 'Symbol', 'Defs', 'ClipPath', 'LinearGradient', 'RadialGradient', 'Stop', 'Mask', 'Pattern', 'Image as SVGImage',
|
|
445
|
+
// SafeArea
|
|
446
|
+
'SafeAreaProvider', 'SafeAreaView', 'SafeAreaListener',
|
|
447
|
+
// Gesture Handler
|
|
448
|
+
'GestureHandlerRootView', 'GestureDetector',
|
|
449
|
+
// Reanimated
|
|
450
|
+
'ReanimatedView', 'ReanimatedText', 'ReanimatedImage', 'ReanimatedScrollView',
|
|
451
|
+
// Common wrapper names
|
|
452
|
+
'Fragment', 'Suspense', 'Provider', 'Consumer', 'Context', 'ForwardRef']);
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Check if a component name is an internal/wrapper component that should be skipped
|
|
456
|
+
*/
|
|
457
|
+
function isInternalComponent(name) {
|
|
458
|
+
if (!name) return true;
|
|
459
|
+
if (INTERNAL_COMPONENT_NAMES.has(name)) return true;
|
|
460
|
+
// Skip anonymous components and common patterns
|
|
461
|
+
if (name === 'Unknown' || name === 'Component') return true;
|
|
462
|
+
// Skip Animated.* wrappers
|
|
463
|
+
if (name.startsWith('Animated')) return true;
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Get the owning React component name for a host fiber.
|
|
469
|
+
* Walks up the fiber tree to find the first user-defined component,
|
|
470
|
+
* skipping React Native internal components.
|
|
471
|
+
*/
|
|
472
|
+
function getOwningComponentName(fiber) {
|
|
473
|
+
if (!fiber) return null;
|
|
474
|
+
|
|
475
|
+
// Walk up the fiber tree looking for a user-defined component
|
|
476
|
+
let current = fiber._debugOwner || fiber.return;
|
|
477
|
+
let depth = 0;
|
|
478
|
+
let firstNonHostName = null;
|
|
479
|
+
while (current && depth < 30) {
|
|
480
|
+
const name = getComponentName(current);
|
|
481
|
+
|
|
482
|
+
// Skip host components (their type is a string like "RCTView")
|
|
483
|
+
if (name && typeof current.type !== 'string') {
|
|
484
|
+
// Remember the first non-host component as fallback
|
|
485
|
+
if (!firstNonHostName) {
|
|
486
|
+
firstNonHostName = name;
|
|
487
|
+
}
|
|
488
|
+
// Return this component if it's not an internal wrapper
|
|
489
|
+
if (!isInternalComponent(name)) {
|
|
490
|
+
return name;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
current = current.return;
|
|
494
|
+
depth++;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Fallback to first non-host component found (even if internal)
|
|
498
|
+
return firstNonHostName;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Check if a stateNode belongs to our dev tools.
|
|
503
|
+
*
|
|
504
|
+
* OPTIMIZED: Uses caching, O(1) Set lookups, and early exit patterns.
|
|
505
|
+
* Walks up fiber tree checking component names (fast) before nativeIDs.
|
|
506
|
+
*
|
|
507
|
+
* When excludeDevTools setting is false, only the Highlight Updates overlay
|
|
508
|
+
* is excluded (to prevent infinite loops). Other dev tools will be highlighted.
|
|
509
|
+
*/
|
|
510
|
+
function isOurOverlayNode(stateNode) {
|
|
511
|
+
const node = stateNode;
|
|
512
|
+
// Fabric: fiber is at canonical.internalInstanceHandle
|
|
513
|
+
// Paper/Legacy: fiber is at _internalInstanceHandle directly on stateNode
|
|
514
|
+
// RN CLI DEV: fiber is at _internalFiberInstanceHandleDEV
|
|
515
|
+
const fiber = node?.canonical?.internalInstanceHandle || node?._internalInstanceHandle || node?._internalFiberInstanceHandleDEV;
|
|
516
|
+
|
|
517
|
+
// Get nativeTag for caching
|
|
518
|
+
const nativeTag = getNativeTag(stateNode) || getNativeTag(node?.canonical?.publicInstance);
|
|
519
|
+
|
|
520
|
+
// Check if we should exclude all dev tools or just highlight overlay
|
|
521
|
+
const excludeDevTools = _RenderTracker.RenderTracker.getSettings().excludeDevTools;
|
|
522
|
+
|
|
523
|
+
// Check cache first (O(1)) - but only for the current excludeDevTools setting
|
|
524
|
+
// When excludeDevTools is false, skip cache to re-evaluate all nodes
|
|
525
|
+
if (nativeTag != null && excludeDevTools) {
|
|
526
|
+
const cached = devToolsNodeCache.get(nativeTag);
|
|
527
|
+
if (cached !== undefined) {
|
|
528
|
+
return cached;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
let result = false;
|
|
532
|
+
|
|
533
|
+
// Fast path: check direct nativeID
|
|
534
|
+
const directNativeID = fiber?.pendingProps?.nativeID || fiber?.memoizedProps?.nativeID || node?.canonical?.currentProps?.nativeID || null;
|
|
535
|
+
|
|
536
|
+
// Always check highlight overlay nativeIDs first (must always be excluded)
|
|
537
|
+
if (isHighlightOverlayNativeID(directNativeID)) {
|
|
538
|
+
result = true;
|
|
539
|
+
} else if (excludeDevTools && isOtherDevToolsNativeID(directNativeID)) {
|
|
540
|
+
// Only check other dev tools if excludeDevTools is enabled
|
|
541
|
+
result = true;
|
|
542
|
+
}
|
|
543
|
+
if (!result && fiber) {
|
|
544
|
+
// Walk up fiber tree - check component names FIRST (they appear early)
|
|
545
|
+
let current = fiber;
|
|
546
|
+
let depth = 0;
|
|
547
|
+
|
|
548
|
+
// Reduced max depth - component names appear by depth 15 typically
|
|
549
|
+
while (current && depth < 30) {
|
|
550
|
+
// Component name check FIRST (faster, appears earlier in tree)
|
|
551
|
+
const name = getComponentName(current);
|
|
552
|
+
if (name) {
|
|
553
|
+
// Always check highlight overlay components (must always be excluded)
|
|
554
|
+
if (HIGHLIGHT_OVERLAY_COMPONENTS.has(name)) {
|
|
555
|
+
result = true;
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
// Check highlight overlay prefixes
|
|
559
|
+
for (const prefix of HIGHLIGHT_OVERLAY_PREFIXES) {
|
|
560
|
+
if (name.startsWith(prefix)) {
|
|
561
|
+
result = true;
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (result) break;
|
|
566
|
+
|
|
567
|
+
// Only check other dev tools if excludeDevTools is enabled
|
|
568
|
+
if (excludeDevTools) {
|
|
569
|
+
if (OTHER_DEV_TOOLS_COMPONENTS.has(name)) {
|
|
570
|
+
result = true;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
// Check other dev tools prefixes
|
|
574
|
+
for (const prefix of OTHER_DEV_TOOLS_PREFIXES) {
|
|
575
|
+
if (name.startsWith(prefix)) {
|
|
576
|
+
result = true;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (result) break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// NativeID check (only if component name didn't match)
|
|
585
|
+
const nativeID = current.pendingProps?.nativeID || current.memoizedProps?.nativeID;
|
|
586
|
+
if (isHighlightOverlayNativeID(nativeID)) {
|
|
587
|
+
result = true;
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
if (excludeDevTools && isOtherDevToolsNativeID(nativeID)) {
|
|
591
|
+
result = true;
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
current = current.return;
|
|
595
|
+
depth++;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Only cache positive results when excludeDevTools is true (default mode)
|
|
600
|
+
// Don't cache when excludeDevTools is false to avoid complex cache invalidation
|
|
601
|
+
if (result && nativeTag != null && excludeDevTools) {
|
|
602
|
+
if (devToolsNodeCache.size >= CACHE_MAX_SIZE) {
|
|
603
|
+
// Clear oldest entries (simple strategy - clear half)
|
|
604
|
+
const entries = Array.from(devToolsNodeCache.keys());
|
|
605
|
+
for (let i = 0; i < CACHE_MAX_SIZE / 2; i++) {
|
|
606
|
+
devToolsNodeCache.delete(entries[i]);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
devToolsNodeCache.set(nativeTag, result);
|
|
610
|
+
}
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Get color based on render count (same algorithm as DevTools)
|
|
616
|
+
* More renders = warmer color (cyan → yellow)
|
|
617
|
+
*/
|
|
618
|
+
function getColorForRenderCount(count) {
|
|
619
|
+
// Clamp to color array bounds
|
|
620
|
+
const index = Math.min(count - 1, COLORS.length - 1);
|
|
621
|
+
return COLORS[Math.max(0, index)];
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Handle traceUpdates event from DevTools backend
|
|
626
|
+
* This is called with a Set of stateNodes that should be highlighted
|
|
627
|
+
*
|
|
628
|
+
* Strategy to prevent infinite loops:
|
|
629
|
+
* 1. Use a rendering lock while rendering highlights
|
|
630
|
+
* 2. Use an isProcessing flag to prevent concurrent processing
|
|
631
|
+
* 3. If Chrome DevTools agent is available, use native overlay (faster, no React)
|
|
632
|
+
* 4. Otherwise, use our React overlay with locks to prevent cascading renders
|
|
633
|
+
*/
|
|
634
|
+
function handleTraceUpdates(nodes) {
|
|
635
|
+
if (nodes.size === 0) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Skip if neither visual toggle nor background tracking is enabled
|
|
640
|
+
if (!globalEnabled && !backgroundTrackingEnabled) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Skip processing when paused - don't count renders or show highlights
|
|
645
|
+
const trackerState = _RenderTracker.RenderTracker.getState();
|
|
646
|
+
if (trackerState.isPaused) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Mark event received for end-to-end timing
|
|
651
|
+
if (_PerformanceLogger.PerformanceLogger.isEnabled()) {
|
|
652
|
+
(0, _PerformanceLogger.markEventReceived)();
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Start performance timing
|
|
656
|
+
const batchSize = _RenderTracker.RenderTracker.getBatchSize();
|
|
657
|
+
const perfTimer = _PerformanceLogger.PerformanceLogger.startBatch(nodes.size, batchSize);
|
|
658
|
+
|
|
659
|
+
// NOTE: Lock-based skipping disabled - relying on nativeID filtering instead
|
|
660
|
+
// If nativeID filtering doesn't work, re-enable this:
|
|
661
|
+
//
|
|
662
|
+
// if (renderingLock || isProcessing) {
|
|
663
|
+
// if (DEBUG_LOGGING) {
|
|
664
|
+
// console.log(`[HighlightUpdates] SKIPPED - lock:${renderingLock} processing:${isProcessing}`);
|
|
665
|
+
// }
|
|
666
|
+
// return;
|
|
667
|
+
// }
|
|
668
|
+
// isProcessing = true;
|
|
669
|
+
|
|
670
|
+
// Process nodes: track render counts and assign colors
|
|
671
|
+
// Filter out our own overlay nodes to prevent infinite loop
|
|
672
|
+
// Also apply user-defined filters to both overlay AND tracking
|
|
673
|
+
const nodesToDraw = [];
|
|
674
|
+
let skippedOverlayCount = 0;
|
|
675
|
+
let skippedByFilterCount = 0;
|
|
676
|
+
const hasActiveFilters = _RenderTracker.RenderTracker.hasActiveFilters();
|
|
677
|
+
for (const stateNode of nodes) {
|
|
678
|
+
if (stateNode && typeof stateNode === "object") {
|
|
679
|
+
try {
|
|
680
|
+
// Skip our own overlay nodes (identified by nativeID)
|
|
681
|
+
if (isOurOverlayNode(stateNode)) {
|
|
682
|
+
skippedOverlayCount++;
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Get nativeTag for tracking render counts
|
|
687
|
+
const publicInstance = getPublicInstance(stateNode);
|
|
688
|
+
const nativeTag = getNativeTag(stateNode) || getNativeTag(publicInstance);
|
|
689
|
+
if (nativeTag == null) {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Extract component info for filtering
|
|
694
|
+
const componentInfo = extractComponentInfo(stateNode);
|
|
695
|
+
|
|
696
|
+
// Apply user-defined filters - skip components that don't pass filters
|
|
697
|
+
// This affects BOTH the overlay highlights AND the render list
|
|
698
|
+
if (hasActiveFilters && !_RenderTracker.RenderTracker.passesFilters(componentInfo)) {
|
|
699
|
+
skippedByFilterCount++;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Check if render counting is enabled
|
|
704
|
+
const showRenderCount = _RenderTracker.RenderTracker.getSettings().showRenderCount;
|
|
705
|
+
let newCount;
|
|
706
|
+
let color;
|
|
707
|
+
if (showRenderCount) {
|
|
708
|
+
// Get current render count and increment (using nativeTag as stable key)
|
|
709
|
+
const currentCount = nodeRenderCounts.get(nativeTag) || 0;
|
|
710
|
+
newCount = currentCount + 1;
|
|
711
|
+
nodeRenderCounts.set(nativeTag, newCount);
|
|
712
|
+
// Assign color based on render count
|
|
713
|
+
color = getColorForRenderCount(newCount);
|
|
714
|
+
} else {
|
|
715
|
+
// Skip counting - use fixed color and count of 0
|
|
716
|
+
newCount = 0;
|
|
717
|
+
color = COLORS[0]; // Use first color (cyan)
|
|
718
|
+
}
|
|
719
|
+
nodesToDraw.push({
|
|
720
|
+
node: stateNode,
|
|
721
|
+
color,
|
|
722
|
+
count: newCount
|
|
723
|
+
});
|
|
724
|
+
} catch {
|
|
725
|
+
// Skip nodes from custom renderers (e.g. React Native Skia) that have
|
|
726
|
+
// non-standard stateNode structures
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Mark filtering phase complete (includes both overlay nodes and user-filtered nodes)
|
|
732
|
+
perfTimer.markFilteringComplete(skippedOverlayCount + skippedByFilterCount, nodesToDraw.length);
|
|
733
|
+
|
|
734
|
+
// Only log if there are real updates (not just overlay re-renders)
|
|
735
|
+
if (nodesToDraw.length === 0) {
|
|
736
|
+
// All nodes were overlay nodes - silently skip
|
|
737
|
+
perfTimer.finish(); // Still log the batch even if empty
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Log actual component updates
|
|
742
|
+
if (DEBUG_LOGGING) {
|
|
743
|
+
console.log(`[HighlightUpdates] ${nodesToDraw.length} component(s) re-rendered`);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// NOTE: We prefer our React overlay for standalone operation.
|
|
747
|
+
// The native overlay only works when Chrome DevTools profiler is actively enabled.
|
|
748
|
+
// Uncomment below to use native overlay when available:
|
|
749
|
+
//
|
|
750
|
+
// const agent = hook?.reactDevtoolsAgent;
|
|
751
|
+
// if (agent?.emit) {
|
|
752
|
+
// if (DEBUG_LOGGING) {
|
|
753
|
+
// console.log(`[HighlightUpdates] Using native overlay (agent available)`);
|
|
754
|
+
// }
|
|
755
|
+
// agent.emit("drawTraceUpdates", nodesToDraw);
|
|
756
|
+
// isProcessing = false;
|
|
757
|
+
// return;
|
|
758
|
+
// }
|
|
759
|
+
|
|
760
|
+
// Check if we should process - either we have a visual callback OR event callbacks
|
|
761
|
+
const hasEventCallbacks = _RenderTracker.RenderTracker.getRenderEventCallbackCount() > 0;
|
|
762
|
+
if (!highlightCallback && !hasEventCallbacks) {
|
|
763
|
+
if (DEBUG_LOGGING) {
|
|
764
|
+
console.log(`[HighlightUpdates] No highlightCallback and no event callbacks - skipping render`);
|
|
765
|
+
}
|
|
766
|
+
isProcessing = false;
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// NOTE: Lock disabled - relying on nativeID filtering instead
|
|
771
|
+
|
|
772
|
+
// Measure each node and call the highlight callback
|
|
773
|
+
// Use batch size from RenderTracker settings (default: 150)
|
|
774
|
+
// Note: batchSize already retrieved above for perfTimer
|
|
775
|
+
perfTimer.markMeasurementStart();
|
|
776
|
+
const measurePromises = nodesToDraw.slice(0, batchSize).map(({
|
|
777
|
+
node: stateNode,
|
|
778
|
+
color,
|
|
779
|
+
count
|
|
780
|
+
}) => new Promise(resolve => {
|
|
781
|
+
const publicInstance = getPublicInstance(stateNode);
|
|
782
|
+
if (!publicInstance) {
|
|
783
|
+
resolve({
|
|
784
|
+
rect: null,
|
|
785
|
+
stateNode,
|
|
786
|
+
color,
|
|
787
|
+
count
|
|
788
|
+
});
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const nativeTag = getNativeTag(stateNode) || getNativeTag(publicInstance);
|
|
792
|
+
if (nativeTag == null) {
|
|
793
|
+
resolve({
|
|
794
|
+
rect: null,
|
|
795
|
+
stateNode,
|
|
796
|
+
color,
|
|
797
|
+
count
|
|
798
|
+
});
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
try {
|
|
802
|
+
publicInstance.measure((x, y, width, height, pageX, pageY) => {
|
|
803
|
+
if (pageX == null || pageY == null || width == null || height == null) {
|
|
804
|
+
resolve({
|
|
805
|
+
rect: null,
|
|
806
|
+
stateNode,
|
|
807
|
+
color,
|
|
808
|
+
count
|
|
809
|
+
});
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
resolve({
|
|
813
|
+
rect: {
|
|
814
|
+
id: nativeTag,
|
|
815
|
+
x: pageX,
|
|
816
|
+
y: pageY,
|
|
817
|
+
width,
|
|
818
|
+
height,
|
|
819
|
+
color,
|
|
820
|
+
count
|
|
821
|
+
},
|
|
822
|
+
stateNode,
|
|
823
|
+
color,
|
|
824
|
+
count
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
} catch (error) {
|
|
828
|
+
resolve({
|
|
829
|
+
rect: null,
|
|
830
|
+
stateNode,
|
|
831
|
+
color,
|
|
832
|
+
count
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}));
|
|
836
|
+
Promise.all(measurePromises).then(results => {
|
|
837
|
+
const validResults = results.filter(r => r.rect !== null);
|
|
838
|
+
const rects = validResults.map(r => r.rect);
|
|
839
|
+
|
|
840
|
+
// Mark measurement phase complete
|
|
841
|
+
perfTimer.markMeasurementComplete(validResults.length, results.length - validResults.length);
|
|
842
|
+
|
|
843
|
+
// Track renders - behavior differs based on what's enabled:
|
|
844
|
+
// - globalEnabled (visual tool): full tracking with storage and list updates
|
|
845
|
+
// - backgroundTrackingEnabled only (Events tool): emit events only, no storage
|
|
846
|
+
const settings = _RenderTracker.RenderTracker.getSettings();
|
|
847
|
+
const trackCauses = settings.trackRenderCauses && settings.showRenderCount;
|
|
848
|
+
let batchNativeTags = null;
|
|
849
|
+
if (trackCauses) {
|
|
850
|
+
batchNativeTags = new Set();
|
|
851
|
+
for (const {
|
|
852
|
+
rect
|
|
853
|
+
} of validResults) {
|
|
854
|
+
if (rect) {
|
|
855
|
+
batchNativeTags.add(rect.id);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (globalEnabled) {
|
|
860
|
+
// Visual tool is on - full tracking with storage and list notifications
|
|
861
|
+
_RenderTracker.RenderTracker.startBatch();
|
|
862
|
+
for (const {
|
|
863
|
+
rect,
|
|
864
|
+
stateNode,
|
|
865
|
+
color,
|
|
866
|
+
count
|
|
867
|
+
} of validResults) {
|
|
868
|
+
if (rect) {
|
|
869
|
+
const componentInfo = extractComponentInfo(stateNode);
|
|
870
|
+
let renderCause;
|
|
871
|
+
if (trackCauses && batchNativeTags) {
|
|
872
|
+
const node = stateNode;
|
|
873
|
+
const fiber = node?.canonical?.internalInstanceHandle;
|
|
874
|
+
renderCause = (0, _RenderCauseDetector.detectRenderCause)(rect.id, fiber, batchNativeTags, settings.debugLogLevel);
|
|
875
|
+
}
|
|
876
|
+
_RenderTracker.RenderTracker.trackRender({
|
|
877
|
+
nativeTag: rect.id,
|
|
878
|
+
viewType: componentInfo.viewType,
|
|
879
|
+
testID: componentInfo.testID,
|
|
880
|
+
nativeID: componentInfo.nativeID,
|
|
881
|
+
accessibilityLabel: componentInfo.accessibilityLabel,
|
|
882
|
+
componentName: componentInfo.componentName,
|
|
883
|
+
measurements: {
|
|
884
|
+
x: rect.x,
|
|
885
|
+
y: rect.y,
|
|
886
|
+
width: rect.width,
|
|
887
|
+
height: rect.height
|
|
888
|
+
},
|
|
889
|
+
color,
|
|
890
|
+
count,
|
|
891
|
+
renderCause
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
_RenderTracker.RenderTracker.endBatch();
|
|
896
|
+
} else {
|
|
897
|
+
// Only background tracking (Events tool) - emit events without storage
|
|
898
|
+
for (const {
|
|
899
|
+
rect,
|
|
900
|
+
stateNode,
|
|
901
|
+
color,
|
|
902
|
+
count
|
|
903
|
+
} of validResults) {
|
|
904
|
+
if (rect) {
|
|
905
|
+
const componentInfo = extractComponentInfo(stateNode);
|
|
906
|
+
let renderCause;
|
|
907
|
+
if (trackCauses && batchNativeTags) {
|
|
908
|
+
const node = stateNode;
|
|
909
|
+
const fiber = node?.canonical?.internalInstanceHandle;
|
|
910
|
+
renderCause = (0, _RenderCauseDetector.detectRenderCause)(rect.id, fiber, batchNativeTags, settings.debugLogLevel);
|
|
911
|
+
}
|
|
912
|
+
_RenderTracker.RenderTracker.emitRenderEvent({
|
|
913
|
+
nativeTag: rect.id,
|
|
914
|
+
viewType: componentInfo.viewType,
|
|
915
|
+
testID: componentInfo.testID,
|
|
916
|
+
nativeID: componentInfo.nativeID,
|
|
917
|
+
accessibilityLabel: componentInfo.accessibilityLabel,
|
|
918
|
+
componentName: componentInfo.componentName,
|
|
919
|
+
measurements: {
|
|
920
|
+
x: rect.x,
|
|
921
|
+
y: rect.y,
|
|
922
|
+
width: rect.width,
|
|
923
|
+
height: rect.height
|
|
924
|
+
},
|
|
925
|
+
color,
|
|
926
|
+
count,
|
|
927
|
+
renderCause
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Mark tracking phase complete
|
|
934
|
+
perfTimer.markTrackingComplete();
|
|
935
|
+
|
|
936
|
+
// Only show visual highlights when the visual tool is enabled (not just background tracking)
|
|
937
|
+
if (rects.length > 0 && highlightCallback && globalEnabled) {
|
|
938
|
+
highlightCallback(rects);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Mark callback phase complete and finish timing
|
|
942
|
+
perfTimer.markCallbackComplete();
|
|
943
|
+
perfTimer.finish();
|
|
944
|
+
}).catch(error => {
|
|
945
|
+
console.error("[HighlightUpdates] Error in measurement pipeline:", error);
|
|
946
|
+
perfTimer.finish(); // Still log timing even on error
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Enable or disable trace updates on all renderer interfaces.
|
|
952
|
+
* This is the key function that makes standalone tracing work!
|
|
953
|
+
*
|
|
954
|
+
* The rendererInterfaces map contains the renderer backend for each React root.
|
|
955
|
+
* Each renderer has a setTraceUpdatesEnabled function that controls whether
|
|
956
|
+
* it emits 'traceUpdates' events when components re-render.
|
|
957
|
+
*
|
|
958
|
+
* See: react-devtools-core/dist/backend.js line 15487-15489
|
|
959
|
+
*/
|
|
960
|
+
function setTraceUpdatesOnRenderers(enabled) {
|
|
961
|
+
if (!hook?.rendererInterfaces) {
|
|
962
|
+
debugLog("No rendererInterfaces available");
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
let count = 0;
|
|
966
|
+
hook.rendererInterfaces.forEach((rendererInterface, rendererId) => {
|
|
967
|
+
if (typeof rendererInterface.setTraceUpdatesEnabled === "function") {
|
|
968
|
+
try {
|
|
969
|
+
rendererInterface.setTraceUpdatesEnabled(enabled);
|
|
970
|
+
count++;
|
|
971
|
+
debugLog(`Renderer ${rendererId}: setTraceUpdatesEnabled(${enabled})`);
|
|
972
|
+
} catch (error) {
|
|
973
|
+
debugLog(`Renderer ${rendererId}: error setting trace updates`, error);
|
|
974
|
+
}
|
|
975
|
+
} else {
|
|
976
|
+
debugLog(`Renderer ${rendererId}: no setTraceUpdatesEnabled method`);
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
debugLog(`Set trace updates ${enabled ? "enabled" : "disabled"} on ${count} renderer(s)`);
|
|
980
|
+
|
|
981
|
+
// Also try the agent if available (in case DevTools is connected)
|
|
982
|
+
if (hook.reactDevtoolsAgent?.setTraceUpdatesEnabled) {
|
|
983
|
+
try {
|
|
984
|
+
hook.reactDevtoolsAgent.setTraceUpdatesEnabled(enabled);
|
|
985
|
+
debugLog("Also set trace updates on agent");
|
|
986
|
+
} catch (error) {
|
|
987
|
+
debugLog("Error setting trace updates on agent", error);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Subscribe to the traceUpdates event from the hook
|
|
994
|
+
*/
|
|
995
|
+
function subscribeToTraceUpdates() {
|
|
996
|
+
if (!hook) return;
|
|
997
|
+
if (traceUpdatesUnsubscribe) return; // Already subscribed
|
|
998
|
+
|
|
999
|
+
debugLog("Subscribing to traceUpdates event");
|
|
1000
|
+
|
|
1001
|
+
// The hook uses EventEmitter pattern with 'sub' method
|
|
1002
|
+
// See: react-devtools-core/dist/backend.js line 17526
|
|
1003
|
+
// hook.sub('traceUpdates', agent.onTraceUpdates)
|
|
1004
|
+
if (typeof hook.sub === "function") {
|
|
1005
|
+
traceUpdatesUnsubscribe = hook.sub("traceUpdates", handleTraceUpdates);
|
|
1006
|
+
debugLog("Subscribed using hook.sub()");
|
|
1007
|
+
} else {
|
|
1008
|
+
debugLog("hook.sub not available, traceUpdates may not work");
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// CRITICAL: Enable trace updates on all renderers
|
|
1012
|
+
// Without this, the renderers won't emit traceUpdates events
|
|
1013
|
+
setTraceUpdatesOnRenderers(true);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Unsubscribe from the traceUpdates event
|
|
1018
|
+
*/
|
|
1019
|
+
function unsubscribeFromTraceUpdates() {
|
|
1020
|
+
// Disable trace updates on renderers first
|
|
1021
|
+
setTraceUpdatesOnRenderers(false);
|
|
1022
|
+
if (traceUpdatesUnsubscribe) {
|
|
1023
|
+
traceUpdatesUnsubscribe();
|
|
1024
|
+
traceUpdatesUnsubscribe = null;
|
|
1025
|
+
debugLog("Unsubscribed from traceUpdates event");
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Set the callback for rendering highlights
|
|
1031
|
+
*/
|
|
1032
|
+
function setHighlightCallback(callback) {
|
|
1033
|
+
highlightCallback = callback;
|
|
1034
|
+
debugLog(`Highlight callback ${callback ? "set" : "cleared"}`);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Set the callback for when a badge is pressed.
|
|
1039
|
+
* This is used to navigate to the detail view when a user taps a render count badge.
|
|
1040
|
+
*/
|
|
1041
|
+
function setBadgePressCallback(callback) {
|
|
1042
|
+
badgePressCallback = callback;
|
|
1043
|
+
debugLog(`Badge press callback ${callback ? "set" : "cleared"}`);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Get the current badge press callback.
|
|
1048
|
+
* Used by the overlay to check if badge presses should be handled.
|
|
1049
|
+
*/
|
|
1050
|
+
function getBadgePressCallback() {
|
|
1051
|
+
return badgePressCallback;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Handle a badge press event.
|
|
1056
|
+
* Calls the registered callback with the nativeTag of the component.
|
|
1057
|
+
*/
|
|
1058
|
+
function handleBadgePress(nativeTag) {
|
|
1059
|
+
if (badgePressCallback) {
|
|
1060
|
+
badgePressCallback(nativeTag);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Spotlight highlight state - for showing which component is being viewed in detail
|
|
1065
|
+
|
|
1066
|
+
let spotlightCallback = null;
|
|
1067
|
+
let currentSpotlightTag = null;
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Set the callback for spotlight highlighting.
|
|
1071
|
+
* Used by the overlay to show a special highlight for the component being viewed.
|
|
1072
|
+
*/
|
|
1073
|
+
function setSpotlightCallback(callback) {
|
|
1074
|
+
spotlightCallback = callback;
|
|
1075
|
+
// If there's an existing spotlight, notify the new callback
|
|
1076
|
+
if (callback && currentSpotlightTag !== null) {
|
|
1077
|
+
callback(currentSpotlightTag);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Set a spotlight highlight on a specific component.
|
|
1083
|
+
* This is used when viewing a component's detail to show which one it is.
|
|
1084
|
+
*/
|
|
1085
|
+
function setSpotlight(nativeTag) {
|
|
1086
|
+
currentSpotlightTag = nativeTag;
|
|
1087
|
+
if (spotlightCallback) {
|
|
1088
|
+
spotlightCallback(nativeTag);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Get the current spotlight nativeTag.
|
|
1094
|
+
*/
|
|
1095
|
+
function getSpotlight() {
|
|
1096
|
+
return currentSpotlightTag;
|
|
1097
|
+
}
|
|
1098
|
+
function notifyStateListeners() {
|
|
1099
|
+
stateListeners.forEach(listener => {
|
|
1100
|
+
try {
|
|
1101
|
+
listener(globalEnabled);
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
console.error("[HighlightUpdates] Error in state listener:", error);
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
function subscribe(listener) {
|
|
1108
|
+
stateListeners.add(listener);
|
|
1109
|
+
listener(globalEnabled);
|
|
1110
|
+
return () => {
|
|
1111
|
+
stateListeners.delete(listener);
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
function initialize() {
|
|
1115
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) {
|
|
1116
|
+
debugLog("Only available in development builds");
|
|
1117
|
+
return false;
|
|
1118
|
+
}
|
|
1119
|
+
if (initialized) {
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1122
|
+
hook = window?.__REACT_DEVTOOLS_GLOBAL_HOOK__ || null;
|
|
1123
|
+
if (!hook) {
|
|
1124
|
+
debugLog("React DevTools hook not found");
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
debugLog("Hook found");
|
|
1128
|
+
debugLog(`Hook has sub: ${typeof hook.sub === "function"}`);
|
|
1129
|
+
debugLog(`Hook has on: ${typeof hook.on === "function"}`);
|
|
1130
|
+
debugLog(`Hook has emit: ${typeof hook.emit === "function"}`);
|
|
1131
|
+
|
|
1132
|
+
// Install profiler interceptor to capture what DevTools detects
|
|
1133
|
+
// This allows us to compare profiler data with our detection
|
|
1134
|
+
(0, _ProfilerInterceptor.installProfilerInterceptor)();
|
|
1135
|
+
|
|
1136
|
+
// Comparison callback available for future use when we re-enable our implementation
|
|
1137
|
+
// setComparisonCallback((profilerNodes: Set<unknown>) => {
|
|
1138
|
+
// debugLog(`[COMPARISON] Profiler detected ${profilerNodes.size} nodes`);
|
|
1139
|
+
// });
|
|
1140
|
+
|
|
1141
|
+
if (hook.rendererInterfaces && hook.rendererInterfaces.size > 0) {
|
|
1142
|
+
initialized = true;
|
|
1143
|
+
debugLog(`Initialized with ${hook.rendererInterfaces.size} renderer(s)`);
|
|
1144
|
+
exposeGlobally();
|
|
1145
|
+
return true;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// Wait for renderer to be available
|
|
1149
|
+
const checkInterval = setInterval(() => {
|
|
1150
|
+
if (hook?.rendererInterfaces && hook.rendererInterfaces.size > 0) {
|
|
1151
|
+
clearInterval(checkInterval);
|
|
1152
|
+
initialized = true;
|
|
1153
|
+
debugLog(`Initialized with ${hook.rendererInterfaces.size} renderer(s) (delayed)`);
|
|
1154
|
+
exposeGlobally();
|
|
1155
|
+
}
|
|
1156
|
+
}, 100);
|
|
1157
|
+
setTimeout(() => clearInterval(checkInterval), 10000);
|
|
1158
|
+
return false;
|
|
1159
|
+
}
|
|
1160
|
+
function exposeGlobally() {
|
|
1161
|
+
if (typeof window !== "undefined") {
|
|
1162
|
+
window.__HIGHLIGHT_UPDATES_CONTROLLER__ = {
|
|
1163
|
+
enable,
|
|
1164
|
+
disable,
|
|
1165
|
+
toggle,
|
|
1166
|
+
isEnabled,
|
|
1167
|
+
setEnabled,
|
|
1168
|
+
subscribe,
|
|
1169
|
+
initialize,
|
|
1170
|
+
destroy,
|
|
1171
|
+
isInitialized: () => initialized,
|
|
1172
|
+
setHighlightCallback
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function enable() {
|
|
1177
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) return;
|
|
1178
|
+
if (!initialized) {
|
|
1179
|
+
initialize();
|
|
1180
|
+
}
|
|
1181
|
+
if (globalEnabled) {
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
debugLog("Enabling highlights");
|
|
1185
|
+
|
|
1186
|
+
// Only subscribe if not already subscribed via background tracking
|
|
1187
|
+
if (!backgroundTrackingEnabled) {
|
|
1188
|
+
subscribeToTraceUpdates();
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// Start tracking renders
|
|
1192
|
+
_RenderTracker.RenderTracker.start();
|
|
1193
|
+
|
|
1194
|
+
// Note: profiler logging disabled for normal operation
|
|
1195
|
+
// enableProfilerLogging();
|
|
1196
|
+
|
|
1197
|
+
globalEnabled = true;
|
|
1198
|
+
notifyStateListeners();
|
|
1199
|
+
(0, _sharedUi.notifySubscriberCountChange)('render');
|
|
1200
|
+
}
|
|
1201
|
+
function disable() {
|
|
1202
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) return;
|
|
1203
|
+
if (!globalEnabled) {
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
debugLog("Disabling highlights");
|
|
1207
|
+
|
|
1208
|
+
// Clear any pending locks/state
|
|
1209
|
+
renderingLock = false;
|
|
1210
|
+
isProcessing = false;
|
|
1211
|
+
if (renderingLockTimeout) {
|
|
1212
|
+
clearTimeout(renderingLockTimeout);
|
|
1213
|
+
renderingLockTimeout = null;
|
|
1214
|
+
}
|
|
1215
|
+
renderedOverlayTags.clear();
|
|
1216
|
+
devToolsNodeCache.clear(); // Clear detection cache
|
|
1217
|
+
(0, _RenderCauseDetector.clearRenderCauseState)(); // Clear render cause state to prevent memory leaks
|
|
1218
|
+
|
|
1219
|
+
// Only unsubscribe if background tracking doesn't need it
|
|
1220
|
+
if (!backgroundTrackingEnabled) {
|
|
1221
|
+
unsubscribeFromTraceUpdates();
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// Stop tracking renders
|
|
1225
|
+
_RenderTracker.RenderTracker.stop();
|
|
1226
|
+
|
|
1227
|
+
// Note: profiler logging disabled for normal operation
|
|
1228
|
+
// disableProfilerLogging();
|
|
1229
|
+
|
|
1230
|
+
globalEnabled = false;
|
|
1231
|
+
notifyStateListeners();
|
|
1232
|
+
(0, _sharedUi.notifySubscriberCountChange)('render');
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Enable background render tracking (for Events tool).
|
|
1237
|
+
* This enables render interception WITHOUT affecting the visual toggle state.
|
|
1238
|
+
* The visual highlight toggle remains independent.
|
|
1239
|
+
*/
|
|
1240
|
+
function enableBackgroundTracking() {
|
|
1241
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) return;
|
|
1242
|
+
if (backgroundTrackingEnabled) {
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
if (!initialized) {
|
|
1246
|
+
initialize();
|
|
1247
|
+
}
|
|
1248
|
+
debugLog("Enabling background tracking");
|
|
1249
|
+
|
|
1250
|
+
// Only subscribe if not already subscribed via main toggle
|
|
1251
|
+
if (!globalEnabled) {
|
|
1252
|
+
subscribeToTraceUpdates();
|
|
1253
|
+
}
|
|
1254
|
+
backgroundTrackingEnabled = true;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Disable background render tracking (for Events tool).
|
|
1259
|
+
* Only actually unsubscribes if the main toggle is also off.
|
|
1260
|
+
*/
|
|
1261
|
+
function disableBackgroundTracking() {
|
|
1262
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) return;
|
|
1263
|
+
if (!backgroundTrackingEnabled) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
debugLog("Disabling background tracking");
|
|
1267
|
+
backgroundTrackingEnabled = false;
|
|
1268
|
+
|
|
1269
|
+
// Only unsubscribe if main toggle is also off
|
|
1270
|
+
if (!globalEnabled) {
|
|
1271
|
+
unsubscribeFromTraceUpdates();
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Check if background tracking is enabled (for debugging)
|
|
1277
|
+
*/
|
|
1278
|
+
function isBackgroundTrackingEnabled() {
|
|
1279
|
+
return backgroundTrackingEnabled;
|
|
1280
|
+
}
|
|
1281
|
+
function toggle() {
|
|
1282
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) return;
|
|
1283
|
+
if (globalEnabled) {
|
|
1284
|
+
disable();
|
|
1285
|
+
} else {
|
|
1286
|
+
enable();
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Clear all render counts and tracked data
|
|
1292
|
+
*/
|
|
1293
|
+
function clearRenderCounts() {
|
|
1294
|
+
nodeRenderCounts.clear();
|
|
1295
|
+
_RenderTracker.RenderTracker.clear();
|
|
1296
|
+
debugLog("Cleared render counts");
|
|
1297
|
+
}
|
|
1298
|
+
function isEnabled() {
|
|
1299
|
+
return globalEnabled;
|
|
1300
|
+
}
|
|
1301
|
+
function setEnabled(enabled) {
|
|
1302
|
+
if (enabled) enable();else disable();
|
|
1303
|
+
}
|
|
1304
|
+
function isInitialized() {
|
|
1305
|
+
return initialized;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// ============================================================================
|
|
1309
|
+
// FREEZE FRAME MODE
|
|
1310
|
+
// ============================================================================
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* Notify all freeze state listeners of a change
|
|
1314
|
+
*/
|
|
1315
|
+
function notifyFreezeListeners() {
|
|
1316
|
+
freezeListeners.forEach(listener => {
|
|
1317
|
+
try {
|
|
1318
|
+
listener(isFrozen);
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
console.error("[HighlightUpdates] Error in freeze listener:", error);
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Subscribe to freeze state changes
|
|
1327
|
+
*/
|
|
1328
|
+
function subscribeToFreeze(listener) {
|
|
1329
|
+
freezeListeners.add(listener);
|
|
1330
|
+
listener(isFrozen);
|
|
1331
|
+
return () => {
|
|
1332
|
+
freezeListeners.delete(listener);
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* Freeze highlights - prevents highlights from fading away.
|
|
1338
|
+
* New renders are still captured but old highlights don't clear.
|
|
1339
|
+
*/
|
|
1340
|
+
function freeze() {
|
|
1341
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) return;
|
|
1342
|
+
if (isFrozen) return;
|
|
1343
|
+
isFrozen = true;
|
|
1344
|
+
debugLog("Freeze mode enabled");
|
|
1345
|
+
notifyFreezeListeners();
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* Unfreeze highlights - resumes normal cleanup behavior.
|
|
1350
|
+
* Clears all current highlights when unfreezing.
|
|
1351
|
+
*/
|
|
1352
|
+
function unfreeze() {
|
|
1353
|
+
if (typeof __DEV__ !== "undefined" && !__DEV__) return;
|
|
1354
|
+
if (!isFrozen) return;
|
|
1355
|
+
isFrozen = false;
|
|
1356
|
+
debugLog("Freeze mode disabled");
|
|
1357
|
+
|
|
1358
|
+
// Clear all current highlights when unfreezing
|
|
1359
|
+
if (highlightCallback) {
|
|
1360
|
+
highlightCallback([]);
|
|
1361
|
+
}
|
|
1362
|
+
notifyFreezeListeners();
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* Toggle freeze state
|
|
1367
|
+
*/
|
|
1368
|
+
function toggleFreeze() {
|
|
1369
|
+
if (isFrozen) {
|
|
1370
|
+
unfreeze();
|
|
1371
|
+
} else {
|
|
1372
|
+
freeze();
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* Check if freeze mode is active
|
|
1378
|
+
*/
|
|
1379
|
+
function getFrozen() {
|
|
1380
|
+
return isFrozen;
|
|
1381
|
+
}
|
|
1382
|
+
function destroy() {
|
|
1383
|
+
if (!initialized) return;
|
|
1384
|
+
unsubscribeFromTraceUpdates();
|
|
1385
|
+
|
|
1386
|
+
// Uninstall profiler interceptor
|
|
1387
|
+
(0, _ProfilerInterceptor.uninstallProfilerInterceptor)();
|
|
1388
|
+
(0, _ProfilerInterceptor.setComparisonCallback)(null);
|
|
1389
|
+
|
|
1390
|
+
// Reset freeze state
|
|
1391
|
+
isFrozen = false;
|
|
1392
|
+
freezeListeners.clear();
|
|
1393
|
+
globalEnabled = false;
|
|
1394
|
+
hook = null;
|
|
1395
|
+
highlightCallback = null;
|
|
1396
|
+
initialized = false;
|
|
1397
|
+
if (typeof window !== "undefined") {
|
|
1398
|
+
delete window.__HIGHLIGHT_UPDATES_CONTROLLER__;
|
|
1399
|
+
}
|
|
1400
|
+
debugLog("Destroyed");
|
|
1401
|
+
}
|
|
1402
|
+
const HighlightUpdatesController = {
|
|
1403
|
+
subscribe,
|
|
1404
|
+
enable,
|
|
1405
|
+
disable,
|
|
1406
|
+
toggle,
|
|
1407
|
+
isEnabled,
|
|
1408
|
+
setEnabled,
|
|
1409
|
+
initialize,
|
|
1410
|
+
destroy,
|
|
1411
|
+
isInitialized,
|
|
1412
|
+
setHighlightCallback,
|
|
1413
|
+
clearRenderCounts,
|
|
1414
|
+
// Freeze frame mode
|
|
1415
|
+
freeze,
|
|
1416
|
+
unfreeze,
|
|
1417
|
+
toggleFreeze,
|
|
1418
|
+
getFrozen,
|
|
1419
|
+
subscribeToFreeze,
|
|
1420
|
+
// Badge press callback for "Click Overlay Badge → Jump to Detail"
|
|
1421
|
+
setBadgePressCallback,
|
|
1422
|
+
getBadgePressCallback,
|
|
1423
|
+
handleBadgePress,
|
|
1424
|
+
// Spotlight highlight for detail view
|
|
1425
|
+
setSpotlightCallback,
|
|
1426
|
+
setSpotlight,
|
|
1427
|
+
getSpotlight,
|
|
1428
|
+
// Cache management for excludeDevTools toggle
|
|
1429
|
+
clearDevToolsCache,
|
|
1430
|
+
// Background tracking for Events tool (separate from visual toggle)
|
|
1431
|
+
enableBackgroundTracking,
|
|
1432
|
+
disableBackgroundTracking,
|
|
1433
|
+
isBackgroundTrackingEnabled
|
|
1434
|
+
};
|
|
1435
|
+
var _default = exports.default = HighlightUpdatesController;
|