все заметки

Chrome Debugger Tracing

2025.05.05

Начало

В некоторых дебаггерах существует tracing с возможностью проверки (condition) значения (например в регистре) в моменте шага (trace into/trace over). То есть, это аналог step into/step over только в автоматическом режиме (без постоянного нажатия кнопок).

И вот захотелось такого же в Chromium браузерах.

Но получилось только в Chrome, так как chrome.sidePanel (это такое окно расширения в окне) отсутствует в Opera и Yandex Browser. Другие не пользую.

Если использовать devtools_page (это внедрение вкладки в devtools панель), то мы получим визуальный tracing, так как нужно открывать devtools и все шаги будут видны. Всякие отдельные окна не рассматривал.

Короче, кому сразу код, то скачать архив: v0.0.0.1.

Или посмотреть исходники:

А далее кому интересны некоторые моменты в коде...

О коде и затыках

Используемые разрешения:

"permissions": [\n\t"debugger",    // дебаггер, основа\n\t"tabs",        // доступ к владкам\n\t"sidePanel",   // панелька\n\t"contextMenus" // контекстное меню, но можно и не нужно\n]

sidePanel, повторюсь, отличная вещь. Главное, не указывать в manifest.json:

"side_panel": {\n\t"default_path": "tracing.html"\n}

Иначе панель будет открываться для всех вкладок. Для осознания сего понадобилось некоторое время.

service_worker используется для внедрения в контекстное меню и открытия панели:

"background": {\n\t"service_worker": "service-worker.js"\n}

Этапы работы:

1. Прикрепляем дебаггер к активной вкладке. Текущая стабильная версия Chrome DevTools Protocol 1.3:

chrome.debugger.attach({tabId}, '1.3', callback);
2. Включаем деббагер:
chrome.debugger.sendCommand({tabId}, 'Debugger.enable');
3. И сразу ставим на паузу:
chrome.debugger.sendCommand({tabId}, 'Debugger.pause');

Если скрипты уже отработали, то это вызывает ошибку. Просто перегружаем страницу.

4. Теперь только step into
chrome.debugger.sendCommand({tabId}, 'Debugger.stepInto');

В котором (пока экспериментально), можно указать скрипты которые нужно пропускать skipList:

[{\n\tscriptId: scriptId,\n\tstart: {lineNumber: startLine, columnNumber: startColumn},\n\tend: {lineNumber: endLine, columnNumber: endColumn}\n}]

Это спасает от ненужных шагов в ненужных скриптах. Вещь!

Но есть проблема, если указать невалидный scriptId, то вываливается ошибка и шаг не происходит. Отваливаются скрипты на странице, и по идее нужно проверять актуальность скрипта. Что увеличивает время выполнения и пока не реализовано.

5. Отслеживаем дебаггерские события
chrome.debugger.onEvent.addListener((source, method, params) => {});
  • source - от какой цели пришло (сравниваем tabId, чужого нам не нужно)
  • method - название события
  • params - данные

Основные события:

  • Debugger.scriptParsed - как только включаем дебаггер, то на каждый скрипт на странице приходит это событие, с полной информацией, например: url (может быть пустой, это значит код в eval).
    И контекст default, isolated, worker - нас интересует только default

    Получить код по scriptId:

    chrome.debugger.sendCommand({tabId}, 'Runtime.getScriptSource', {scriptId});
  • Debugger.paused - конечно пауза. В params все данные на текущем шаге.
    params.callFrames[].scopeChain[] - весь доступный scope
    params.callFrames[].returnValue - если есть, то значит данные из функции return data;
    Это может быть и значение типа string, number, etc, либо объект с objectId, данные которого получаем из
    chrome.debugger.sendCommand({tabId}, 'Runtime.getProperties', {objectId: object.objectId});

    В котором так же может быть object, то есть, нужна рекурсия.

    Так же, если область поиска global, то для скорости обхода нужно исключить все переменные window Object.keys(window) и встроенные функции.

    Есть проблема. Объекта с objectId который выдает дебаггер, может не существовать.

    {"code":-32000,"message":"Could not find object with given id"}

    Вообще, с этими ошибками есть непонятки. Code может быть один и тот же:

    {"code":-32000,"message":"No script with passed id."}\n{"code":-32000,"message":"Can only perform operation while paused."}

    Вторая проблема это значение текущей позиции: params.callFrames[].location, params.callFrames[].scopeChain[].endLocation, params.callFrames[].scopeChain[].startLocation.

    Иногда похоже на отдаленную реальность, иногда они выходят за границы кода.
    То есть, location может указывать на строку 12, но если получить код, то в нем всего 8 строк. Поэтому может вылететь: alert('bad location');

Интерфейс

  • Основные кнопки для управления:
    • attach/detach - включаем/выключаем
    • trace/pause - шагаем/останавливаемся
    • счетчик шагов
    • reset - F5 для этой панели
  • js scripts

    Скрипты, которые используются в данной вкладке. Можно отметить для пропуска и посмотреть исходной код.

  • search in scope

    Что ищем, то и найдем. И если найдено, то можно остановить прогулку, и посмотреть, где это.

    Так же существует возможность поставить breakpoint, но его не будет в списке у вкладки.

    chrome.debugger.sendCommand({tabId}, 'Debugger.setBreakpointByUrl', {lineNumber, columnNumber, scriptHash});
  • result
    Список найденного. И сохранять ли его после перезагрузки страницы.

А выглядит вот так:

Chrome Debugger Tracing sidePanel

Страница для теста: ищем test, в одном случае это значение из base64, а в другом это конкатенация.

P.S. Сыро, очень сыро, как и сам протокол Chrome DevTools...

еще по теме extension