UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

223 lines • 8.36 kB
/** * Flutter Skia Inspector * Provides deep inspection of Skia/CanvasKit drawing operations */ export class FlutterSkiaInspector { static frames = []; static isRecording = false; /** * Start recording Skia draw commands */ static async startRecording(page) { this.frames = []; this.isRecording = true; await page.evaluate(() => { // Hook into CanvasKit if available const canvasKitHooks = [ 'window._flutter?.canvasKit', 'window.flutterCanvasKit', 'window.CanvasKitInit' ]; let canvasKit = null; for (const hook of canvasKitHooks) { try { canvasKit = eval(hook); if (canvasKit) break; } catch (e) { } } if (!canvasKit) { console.warn('CanvasKit not found - trying alternative approach'); // Alternative: Hook into canvas context methods const canvases = document.querySelectorAll('canvas'); canvases.forEach((canvas, index) => { const ctx = canvas.getContext('2d') || canvas.getContext('webgl') || canvas.getContext('webgl2'); if (!ctx) return; // console.log(`Hooking canvas ${index} with context type: ${ctx.constructor.name}`); // For WebGL contexts (used by CanvasKit) if (ctx instanceof WebGLRenderingContext || ctx instanceof WebGL2RenderingContext) { // Hook common WebGL methods const methods = ['drawArrays', 'drawElements', 'clear', 'flush']; methods.forEach(method => { const original = ctx[method]; if (typeof original === 'function') { ctx[method] = function (...args) { // console.log(`WebGL.${method}:`, args); window.postMessage({ type: 'skia-draw-command', command: { type: `WebGL.${method}`, params: args, timestamp: performance.now() } }, '*'); return original.apply(this, args); }; } }); } }); } // Hook into requestAnimationFrame to track frames const originalRAF = window.requestAnimationFrame; let frameNumber = 0; let frameStartTime = 0; window.requestAnimationFrame = function (callback) { return originalRAF.call(window, (timestamp) => { frameStartTime = performance.now(); // Notify frame start window.postMessage({ type: 'skia-frame-start', frameNumber: frameNumber++, timestamp: timestamp }, '*'); // Wrap callback try { callback(timestamp); } finally { // Notify frame end const duration = performance.now() - frameStartTime; window.postMessage({ type: 'skia-frame-end', duration: duration }, '*'); } }); }; }); // Listen for draw commands await page.evaluate(() => { window.addEventListener('message', (event) => { if (event.data.type?.startsWith('skia-')) { // Store in window for retrieval window.__skiaDebugData = window.__skiaDebugData || []; window.__skiaDebugData.push(event.data); } }); }); } /** * Stop recording and get captured frames */ static async stopRecording(page) { this.isRecording = false; // Retrieve recorded data const debugData = await page.evaluate(() => { return window.__skiaDebugData || []; }); // Process into frames let currentFrame = null; const frames = []; for (const data of debugData) { if (data.type === 'skia-frame-start') { if (currentFrame) { frames.push(currentFrame); } currentFrame = { frameNumber: data.frameNumber, timestamp: data.timestamp, drawCommands: [], duration: 0, layerCount: 0 }; } else if (data.type === 'skia-frame-end' && currentFrame) { currentFrame.duration = data.duration; } else if (data.type === 'skia-draw-command' && currentFrame) { currentFrame.drawCommands.push(data.command); } } if (currentFrame) { frames.push(currentFrame); } this.frames = frames; return frames; } /** * Get a specific frame's draw commands */ static getFrame(frameNumber) { return this.frames.find(f => f.frameNumber === frameNumber); } /** * Analyze performance across frames */ static analyzePerformance() { if (this.frames.length === 0) { return { averageFrameTime: 0, maxFrameTime: 0, droppedFrames: 0, fps: 0 }; } const frameTimes = this.frames.map(f => f.duration); const averageFrameTime = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length; const maxFrameTime = Math.max(...frameTimes); const droppedFrames = frameTimes.filter(t => t > 16.67).length; // 60fps threshold const fps = 1000 / averageFrameTime; return { averageFrameTime, maxFrameTime, droppedFrames, fps }; } /** * Export frames for external analysis */ static exportFrames() { return JSON.stringify(this.frames, null, 2); } /** * Inject visual debugging overlay for Skia operations */ static async injectSkiaDebugOverlay(page) { await page.evaluate(() => { // Create debug info panel const panel = document.createElement('div'); panel.id = 'skia-debug-panel'; panel.style.cssText = ` position: fixed; top: 10px; right: 10px; width: 300px; background: rgba(0, 0, 0, 0.8); color: white; padding: 10px; font-family: monospace; font-size: 12px; z-index: 999999; border-radius: 5px; max-height: 400px; overflow-y: auto; `; document.body.appendChild(panel); // Update panel with frame info let frameCount = 0; let lastFrameTime = performance.now(); const updatePanel = () => { const now = performance.now(); const frameTime = now - lastFrameTime; const fps = 1000 / frameTime; panel.innerHTML = ` <h3>Skia Debug Info</h3> <div>Frame: ${frameCount++}</div> <div>FPS: ${fps.toFixed(1)}</div> <div>Frame Time: ${frameTime.toFixed(2)}ms</div> <div>Canvas Count: ${document.querySelectorAll('canvas').length}</div> <hr> <div id="skia-commands"></div> `; lastFrameTime = now; requestAnimationFrame(updatePanel); }; updatePanel(); }); } } //# sourceMappingURL=flutter-skia-inspector.js.map