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
JavaScript
/**
* 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