|
@@ -0,0 +1,221 @@
|
|
|
|
|
+const { chromium } = require('@playwright/test');
|
|
|
|
|
+
|
|
|
|
|
+class DebuggerSync {
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ this.sessions = [];
|
|
|
|
|
+ this.breakpoints = new Map();
|
|
|
|
|
+ this.isEnabled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async registerSession(page, sessionId) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const cdpSession = await page.context().newCDPSession(page);
|
|
|
|
|
+
|
|
|
|
|
+ await cdpSession.send('Debugger.enable');
|
|
|
|
|
+ await cdpSession.send('Runtime.enable');
|
|
|
|
|
+
|
|
|
|
|
+ const session = {
|
|
|
|
|
+ id: sessionId,
|
|
|
|
|
+ page: page,
|
|
|
|
|
+ cdp: cdpSession,
|
|
|
|
|
+ breakpoints: new Set()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ cdpSession.on('Debugger.paused', (params) => {
|
|
|
|
|
+ this.onDebuggerPaused(sessionId, params);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ cdpSession.on('Debugger.resumed', (params) => {
|
|
|
|
|
+ this.onDebuggerResumed(sessionId, params);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.sessions.push(session);
|
|
|
|
|
+
|
|
|
|
|
+ return session;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async enableSync() {
|
|
|
|
|
+ this.isEnabled = true;
|
|
|
|
|
+
|
|
|
|
|
+ // Настраиваем перехват точек останова для всех сессий
|
|
|
|
|
+ for (const session of this.sessions) {
|
|
|
|
|
+ await this.setupBreakpointInterception(session);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Настраиваем перехват точек останова
|
|
|
|
|
+ async setupBreakpointInterception(session) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Перехватываем установку точек останова через DevTools UI
|
|
|
|
|
+ await session.page.addInitScript(() => {
|
|
|
|
|
+ // Перехватываем методы DevTools для установки breakpoints
|
|
|
|
|
+ if (window.DevToolsAPI) {
|
|
|
|
|
+ const originalSetBreakpoint = window.DevToolsAPI.setBreakpoint;
|
|
|
|
|
+ window.DevToolsAPI.setBreakpoint = function(...args) {
|
|
|
|
|
+ // Отправляем событие о новой точке останова
|
|
|
|
|
+ window.postMessage({
|
|
|
|
|
+ type: 'BREAKPOINT_SET',
|
|
|
|
|
+ data: args
|
|
|
|
|
+ }, '*');
|
|
|
|
|
+ return originalSetBreakpoint.apply(this, args);
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Слушаем сообщения о точках останова
|
|
|
|
|
+ await session.page.exposeFunction('notifyBreakpointChange', (data) => {
|
|
|
|
|
+ this.handleBreakpointChange(session.id, data);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`Failed to setup breakpoint interception for ${session.id}:`, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Обработка изменения точек останова
|
|
|
|
|
+ async handleBreakpointChange(sessionId, data) {
|
|
|
|
|
+ if (!this.isEnabled) return;
|
|
|
|
|
+
|
|
|
|
|
+ const { url, lineNumber, columnNumber, condition } = data;
|
|
|
|
|
+ const breakpointId = `${url}:${lineNumber}:${columnNumber}`;
|
|
|
|
|
+
|
|
|
|
|
+ // Сохраняем точку останова
|
|
|
|
|
+ this.breakpoints.set(breakpointId, {
|
|
|
|
|
+ url,
|
|
|
|
|
+ lineNumber,
|
|
|
|
|
+ columnNumber,
|
|
|
|
|
+ condition,
|
|
|
|
|
+ setBy: sessionId
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Синхронизируем с другими сессиями
|
|
|
|
|
+ await this.syncBreakpointToOtherSessions(sessionId, breakpointId, data);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Синхронизируем точку останова с другими сессиями
|
|
|
|
|
+ async syncBreakpointToOtherSessions(originSessionId, breakpointId, breakpointData) {
|
|
|
|
|
+ const { url, lineNumber, columnNumber, condition } = breakpointData;
|
|
|
|
|
+
|
|
|
|
|
+ for (const session of this.sessions) {
|
|
|
|
|
+ if (session.id === originSessionId) continue;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Устанавливаем точку останова через CDP
|
|
|
|
|
+ const response = await session.cdp.send('Debugger.setBreakpointByUrl', {
|
|
|
|
|
+ lineNumber: lineNumber - 1, // CDP использует 0-based индексацию
|
|
|
|
|
+ url: url,
|
|
|
|
|
+ columnNumber: columnNumber,
|
|
|
|
|
+ condition: condition
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ session.breakpoints.add(breakpointId);
|
|
|
|
|
+ console.log(`🔄 Synced breakpoint ${breakpointId} to session ${session.id}`);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`Failed to sync breakpoint to session ${session.id}:`, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Обработка паузы отладчика
|
|
|
|
|
+ async onDebuggerPaused(sessionId, params) {
|
|
|
|
|
+ if (!this.isEnabled) return;
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`⏸️ Debugger paused in session ${sessionId}`);
|
|
|
|
|
+
|
|
|
|
|
+ // Приостанавливаем выполнение во всех других сессиях
|
|
|
|
|
+ await this.pauseAllOtherSessions(sessionId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Обработка возобновления отладчика
|
|
|
|
|
+ async onDebuggerResumed(sessionId, params) {
|
|
|
|
|
+ if (!this.isEnabled) return;
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`▶️ Debugger resumed in session ${sessionId}`);
|
|
|
|
|
+
|
|
|
|
|
+ // Возобновляем выполнение во всех других сессиях
|
|
|
|
|
+ await this.resumeAllOtherSessions(sessionId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Приостанавливаем все остальные сессии
|
|
|
|
|
+ async pauseAllOtherSessions(originSessionId) {
|
|
|
|
|
+ const pausePromises = this.sessions
|
|
|
|
|
+ .filter(session => session.id !== originSessionId)
|
|
|
|
|
+ .map(async session => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await session.cdp.send('Debugger.pause');
|
|
|
|
|
+ console.log(`⏸️ Paused session ${session.id}`);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`Failed to pause session ${session.id}:`, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await Promise.all(pausePromises);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Возобновляем все остальные сессии
|
|
|
|
|
+ async resumeAllOtherSessions(originSessionId) {
|
|
|
|
|
+ const resumePromises = this.sessions
|
|
|
|
|
+ .filter(session => session.id !== originSessionId)
|
|
|
|
|
+ .map(async session => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await session.cdp.send('Debugger.resume');
|
|
|
|
|
+ console.log(`▶️ Resumed session ${session.id}`);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`Failed to resume session ${session.id}:`, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await Promise.all(resumePromises);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Удаляем точку останова из всех сессий
|
|
|
|
|
+ async removeBreakpoint(breakpointId) {
|
|
|
|
|
+ this.breakpoints.delete(breakpointId);
|
|
|
|
|
+
|
|
|
|
|
+ for (const session of this.sessions) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Находим и удаляем точку останова
|
|
|
|
|
+ const [url, lineNumber] = breakpointId.split(':');
|
|
|
|
|
+ await session.cdp.send('Debugger.removeBreakpoint', {
|
|
|
|
|
+ breakpointId: breakpointId
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ session.breakpoints.delete(breakpointId);
|
|
|
|
|
+ console.log(`🗑️ Removed breakpoint ${breakpointId} from session ${session.id}`);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`Failed to remove breakpoint from session ${session.id}:`, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Отключаем синхронизацию
|
|
|
|
|
+ async disableSync() {
|
|
|
|
|
+ this.isEnabled = false;
|
|
|
|
|
+ console.log('🔄 Breakpoint synchronization disabled');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Очистка ресурсов
|
|
|
|
|
+ async cleanup() {
|
|
|
|
|
+ this.isEnabled = false;
|
|
|
|
|
+
|
|
|
|
|
+ for (const session of this.sessions) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await session.cdp.send('Debugger.disable');
|
|
|
|
|
+ await session.cdp.detach();
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`Error during cleanup for session ${session.id}:`, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.sessions = [];
|
|
|
|
|
+ this.breakpoints.clear();
|
|
|
|
|
+ console.log('🧹 Debugger sync cleaned up');
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module.exports = DebuggerSync;
|