Chrome extension for real-time frontend performance measurement. Provides a modal overlay and DevTools panel dashboard to help frontend developers monitor Web Vitals, network requests, DOM interactions, FPS, and memory usage.
- Web Vitals — TTFB, FCP, LCP, CLS, INP with threshold ratings (good/warning/bad) and descriptions
- Customizable Thresholds — Adjust good/warn thresholds per metric via settings panel (gear icon). Persisted across sessions
- Network Monitoring — Intercepts fetch, XHR, WebSocket, and SSE with timing and status
- Interaction Tracking — Measures click/keydown/input to DOM mutation delay + per-interaction heap delta
- FPS & Long Frames — requestAnimationFrame-based FPS counter + Long Animation Frame (LoAF) detection with script attribution
- JS Heap Memory — Tracks usedJSHeapSize, totalJSHeapSize, and usage percentage in real-time
- React Component Tracking — Auto-detects React, tracks per-component render counts, identifies re-render causes (prop/hook/parent), cascade mapping (trigger → affected components)
- Network→Render Correlation — Links API responses to React commits within 500ms window
- CSS Inspector — Click-to-inspect mode with element highlighting; shows matched CSS rules sorted by specificity, inline styles,
!importantflags, and inherited properties - DOM Tree View — Structure snapshot around the inspected element (ancestors + descendants) with element path generation
- Dual View — Modal overlay (icon click) + DevTools panel (PerfPilot tab)
- Cookie Storage — Persists measurement data and settings in cookies with
perfpilot_namespace
[Extension Icon Click] [Open DevTools]
│ │
▼ ▼
background.js devtools.js
(Service Worker) → panel.html
│ │
▼ │
content.js (ISOLATED) │
├── Modal overlay (Shadow DOM) │
├── Bridge (postMessage → runtime)──────┘
└── Cookie storage
│
inject.js (MAIN world)
├── Web Vitals (PerformanceObserver)
├── Network (fetch/XHR/WS/SSE monkey-patch)
├── DOM Observer (MutationObserver)
├── FPS (rAF + LoAF)
├── Memory (performance.memory)
└── CSS Inspector / DOM Tree (inspect mode)
How a metric travels from the page to the dashboards:
-
Injection —
content.js(ISOLATED world,run_at: document_start) asks the service worker to injectinject.jsinto the MAIN world viachrome.scripting.executeScript. Collectors must live in the MAIN world to reach page-level objects: React fiber roots,performance.memory, and the page's ownfetch/XMLHttpRequestto monkey-patch. -
Collection —
inject.jswires up the collectors, each observing a different signal source:
| Collector | Source API | Events emitted |
|---|---|---|
web-vitals.js |
PerformanceObserver |
web-vital |
network.js |
fetch/XHR/WebSocket/SSE monkey-patch |
network-request |
dom-observer.js |
MutationObserver + click/keydown/input listeners |
interaction, dom-mutation |
fps.js |
requestAnimationFrame loop + LoAF observer |
fps, long-frame |
memory.js |
performance.memory polling (2s interval) |
memory |
react-tracker.js |
__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot (installs the hook, or wraps the existing one if React DevTools is present) |
react-detected, react-commit, react-stats |
css-inspector.js + dom-tree.js |
getComputedStyle + stylesheet rule matching, DOM traversal |
inspect results |
-
MAIN → ISOLATED bridge — every event leaves the MAIN world as
window.postMessage({ source: 'perf-pilot', type, payload }).bridge.jsin the ISOLATED world filters bysourceandevent.source === window, then dispatches to type-based listeners registered bycontent.js. -
Fan-out —
content.jsconsumes each event twice: it updates the modal dashboard (Shadow DOM) directly, and forwards the same message viachrome.runtime.sendMessageto the service worker. -
Routing to DevTools —
background.jskeeps atabId → portmap of connected DevTools panels (devtools-panelports). Incoming messages are routed to the panel of the originating tab only, so multiple tabs can be measured independently. -
Persistence —
storage.jssaves measurement data and settings as cookies under theperfpilot_namespace (≤3800 bytes per key; older entries are trimmed when over the limit), so data survives reloads without any external server. Nothing leaves the browser — there is no remote collection endpoint.
npm install
npm run build- Open
chrome://extensions - Enable Developer mode
- Click Load unpacked → select the
dist/folder - Visit any website → click the PerfPilot icon for the modal overlay
- Open DevTools → navigate to the PerfPilot tab
npm run dev # watch mode (rebuilds on file changes)
npm run build # production buildsrc/
├── manifest.json # Chrome MV3 manifest
├── background.js # Service worker (icon click + port routing)
├── icons/ # Extension icons (16, 48, 128px)
├── inject/
│ ├── inject.js # MAIN world entry (aggregates collectors)
│ └── collectors/
│ ├── web-vitals.js # TTFB, FCP, LCP, CLS, INP
│ ├── network.js # fetch/XHR/WebSocket/SSE intercept
│ ├── dom-observer.js # MutationObserver + interaction timing
│ ├── react-tracker.js # React hook setup + commit tracking
│ ├── fiber-utils.js # Fiber tree traversal + render cause analysis
│ ├── fps.js # rAF FPS + LoAF
│ ├── memory.js # JS heap memory tracking
│ ├── css-inspector.js # Inspect mode + rule matching, specificity, inheritance
│ └── dom-tree.js # DOM structure snapshot + element path
├── content/
│ ├── content.js # ISOLATED world entry + modal dashboard
│ ├── bridge.js # MAIN ↔ ISOLATED message bridge
│ └── storage.js # Cookie-based data persistence
└── devtools/
├── devtools.html # DevTools entry page
├── devtools.js # Panel creation
├── panel.html # Panel HTML
├── panel.css # Panel styles
└── panel.js # Port connection + dashboard rendering
- Vanilla JS — No framework, minimal bundle size
- Vite — Build tool with multi-entry rollup config
- Chrome Extension MV3 — Manifest V3, service worker, content scripts
- Shadow DOM — Modal overlay isolated from page CSS
- Chrome 123+ (for Long Animation Frame API)
- Chrome 116+ (for all other features)
performance.memoryis Chrome-specific (memory section gracefully hidden in other browsers)
Phase 1 — Universal performance metrics (Web Vitals, Network, FPS, Memory)✅Phase 2 — React component render tracking via fiber tree✅Phase 3 — CSS debugging + DOM element structure analysis✅