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
218 lines • 8.3 kB
JavaScript
/**
* Flutter Canvas Inspector
* Uses Flutter's built-in debug capabilities to inspect CanvasKit rendering
*/
export class FlutterCanvasInspector {
/**
* Enable Flutter's debug paint modes
*/
static async enableDebugPaint(page) {
await page.evaluate(() => {
// Try to access Flutter's debug flags through various methods
// Method 1: Through window.flutter
if (window.flutter) {
const flutter = window.flutter;
if (flutter.debugPaintSizeEnabled !== undefined) {
flutter.debugPaintSizeEnabled = true;
}
}
// Method 2: Through global debug flags
if (window.debugPaintSizeEnabled !== undefined) {
window.debugPaintSizeEnabled = true;
}
// Method 3: Through Flutter's internal APIs
const flutterInternals = [
'_flutter',
'flutter_internalWebGLVersion',
'flutterCanvasKit',
'__flutter__'
];
for (const prop of flutterInternals) {
if (window[prop]) {
// console.log(`Found Flutter internal: ${prop}`);
}
}
});
}
/**
* Try to access Flutter's render tree
*/
static async getRenderTree(page) {
return await page.evaluate(() => {
// Look for Flutter's render tree in various locations
const possiblePaths = [
'window.flutter.renderTree',
'window._flutter.renderTree',
'window.flutterRenderer',
'document.querySelector("flutter-view")?.renderTree'
];
for (const path of possiblePaths) {
try {
const result = eval(path);
if (result)
return result;
}
catch (e) {
// Continue searching
}
}
return null;
});
}
/**
* Enable semantics if not already enabled
*/
static async enableSemantics(page) {
return await page.evaluate(() => {
// Try to enable semantics through various methods
// Method 1: Click the accessibility button if present
const accessibilityButton = document.querySelector('[aria-label*="accessibility" i]');
if (accessibilityButton) {
accessibilityButton.click();
return true;
}
// Method 2: Through Flutter APIs
if (window.flutter?.semanticsEnabled !== undefined) {
window.flutter.semanticsEnabled = true;
return true;
}
// Method 3: Dispatch custom event
window.dispatchEvent(new CustomEvent('flutter-enable-semantics'));
return false;
});
}
/**
* Get Flutter widget inspector data if available
*/
static async getWidgetInspectorData(page) {
return await page.evaluate(() => {
// Try to access widget inspector service
const inspectorPaths = [
'window.flutter?.widgetInspectorService',
'window._widgetInspectorService',
'window.flutterWidgetInspector'
];
for (const path of inspectorPaths) {
try {
const inspector = eval(path);
if (inspector) {
return {
available: true,
methods: Object.getOwnPropertyNames(inspector).filter(prop => typeof inspector[prop] === 'function')
};
}
}
catch (e) {
// Continue searching
}
}
return { available: false };
});
}
/**
* Inject a custom overlay to visualize Flutter elements
*/
static async injectDebugOverlay(page) {
await page.evaluate(() => {
// Create overlay canvas
const overlay = document.createElement('canvas');
overlay.id = 'flutter-debug-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 999999;
`;
document.body.appendChild(overlay);
// Set up canvas context
const ctx = overlay.getContext('2d');
if (!ctx)
return;
overlay.width = window.innerWidth;
overlay.height = window.innerHeight;
// Function to draw semantic node bounds
const drawSemanticBounds = () => {
ctx.clearRect(0, 0, overlay.width, overlay.height);
const semanticNodes = document.querySelectorAll('[role], [aria-label], flt-semantics');
semanticNodes.forEach((node) => {
const rect = node.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
// Draw bounding box
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
// Draw label
const label = node.getAttribute('aria-label') || node.getAttribute('role') || '';
if (label) {
ctx.fillStyle = 'red';
ctx.font = '12px Arial';
ctx.fillText(label, rect.x, rect.y - 5);
}
}
});
};
// Update overlay on mutations
const observer = new MutationObserver(drawSemanticBounds);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['aria-label', 'role']
});
// Initial draw
drawSemanticBounds();
});
}
/**
* Try to hook into Flutter's rendering pipeline
*/
static async hookRenderingPipeline(page) {
await page.evaluate(() => {
// Attempt to intercept Flutter's requestAnimationFrame calls
const originalRAF = window.requestAnimationFrame;
let frameCount = 0;
window.requestAnimationFrame = function (callback) {
frameCount++;
// Wrap the callback to intercept render frames
const wrappedCallback = (timestamp) => {
// console.log(`Flutter frame ${frameCount} at ${timestamp}`);
// Try to capture render tree state
if (window.flutter?.getRenderTree) {
const renderTree = window.flutter.getRenderTree();
// console.log('Render tree:', renderTree);
}
// Call original callback
callback(timestamp);
};
return originalRAF.call(window, wrappedCallback);
};
});
}
/**
* Get comprehensive Flutter debug info
*/
static async getDebugInfo(page) {
const info = {
debugPaintEnabled: false,
semanticsEnabled: false
};
// Check debug paint status
info.debugPaintEnabled = await page.evaluate(() => {
return window.debugPaintSizeEnabled === true ||
window.flutter?.debugPaintSizeEnabled === true;
});
// Check semantics status
info.semanticsEnabled = await page.evaluate(() => {
return document.querySelectorAll('flt-semantics').length > 0;
});
// Try to get render tree
info.renderTree = await this.getRenderTree(page);
// Try to get widget inspector data
info.widgetTree = await this.getWidgetInspectorData(page);
return info;
}
}
//# sourceMappingURL=flutter-canvas-inspector.js.map