Основной код расширения. Комментарии присутствуют.
let tabId;\nchrome.tabs.query({active: true, currentWindow: true}, function (tabs) {\n\ttabId = tabs[0].id;\n});\n\n// для генерации хэша строки\nconst crcTable = [];\nfor (let n= 0; n < 256; n++){\n\tlet c = n;\n\tfor (let k =0; k < 8; k++){\n\t\tc = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));\n\t}\n\tcrcTable[n] = c;\n}\nconst crc32 = function(str) {\n\tlet crc = 0 ^ (-1);\n\tfor (let i = 0; i < str.length; i++ ) {\n\t\tcrc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];\n\t}\n\treturn (crc ^ (-1)) >>> 0;\n};\n\n// перезагрузка панели\ndocument.querySelector('#reset').addEventListener('click', async () => {\n\tif (attach.on) {\n\t\tawait chrome.debugger.detach({tabId});\n\t}\n\twindow.location.reload();\n});\n\n// аксессоры для attach\nconst attach = {\n\tblock: document.querySelector('#attach'),\n\t_on: false,\n\tget on() {\n\t\treturn this._on;\n\t},\n\tset on(value) {\n\t\tthis._on = value;\n\t\tif (value) {\n\t\t\tthis.block.textContent = 'detach';\n\t\t\ttrace.on = false;\n\t\t} else {\n\t\t\tthis.block.textContent = 'attach';\n\t\t\ttrace.on = true;\n\t\t}\n\t}\n};\n\nattach.block.addEventListener('click', () => {\n\tif (attach.on) {\n\t\tchrome.debugger.detach({tabId},\n\t\t\t() => {\n\t\t\t\tif (chrome.runtime.lastError) {\n\t\t\t\t\tconsole.error(chrome.runtime.lastError);\n\t\t\t\t}\n\t\t\t\tattach.on = false;\n\t\t\t\tsteps.count = 0;\n\t\t\t});\n\t} else {\n\t\tscripts.clear();\n\t\tchrome.debugger.attach({tabId}, '1.3', () => {\n\t\t\tchrome.debugger.sendCommand(\n\t\t\t\t{tabId},\n\t\t\t\t'Debugger.enable',\n\t\t\t\t{},\n\t\t\t\t(e) => {\n\t\t\t\t\tif (chrome.runtime.lastError) {\n\t\t\t\t\t\tconsole.error(chrome.runtime.lastError);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tattach.on = true;\n\t\t\t\t\t\tcommand('Debugger.pause');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t});\n\t}\n});\n\n// аксессоры для trace\nconst trace = {\n\tblock: document.querySelector('#trace'),\n\t_on: false,\n\tget on() {\n\t\treturn this._on;\n\t},\n\tset on(value) {\n\t\tif (!attach.on) {\n\t\t\tvalue = false;\n\t\t}\n\t\tthis.block.disabled = !attach.on;\n\t\tthis._on = value;\n\t\tif (value) {\n\t\t\tthis.block.textContent = 'pause';\n\t\t} else {\n\t\t\tthis.block.textContent = 'trace';\n\t\t}\n\t}\n};\n\ntrace.block.addEventListener('click', () => {\n\tif (trace.on) {\n\t\ttrace.on = false;\n\t\ttrace.block.textContent = 'wait';\n\t\ttrace.block.disabled = true;\n\t\tcommand('Debugger.stepInto',{}, () => {\n\t\t\ttrace.on = false;\n\t\t});\n\t} else {\n\t\ttrace.on = true;\n\t\tstepInto();\n\t}\n});\n\n// аксессоры для steps\nconst steps = {\n\tblock: document.querySelector('#steps'),\n\t_count: 0,\n\tget count() {\n\t\treturn this._count;\n\t},\n\tset count(value) {\n\t\tthis._count = value;\n\t\tthis.block.textContent = this._count;\n\t}\n};\n\n// обработка и отображение скриптов\nconst scripts = {\n\tblock: document.querySelector('#scripts'),\n\tlist: {},\n\tget skip() {\n\t\tlet skip = [];\n\t\tthis.block.ul.querySelectorAll('input[type="checkbox"]:checked').forEach(e => {skip.push(e.value)});\n\t\treturn skip;\n\t},\n\tget pause() {\n\t\treturn this._pause.checked;\n\t},\n\tget autoskip() {\n\t\treturn this._autoskip.checked;\n\t},\n\tadd(value, autoskip) {\n\t\tthis.list[value.scriptId] = value;\n\n\t\tlet li = document.createElement('li');\n\n\t\tlet checkbox = document.createElement('input');\n\t\tcheckbox.setAttribute('type', 'checkbox');\n\t\tcheckbox.setAttribute('title', 'skip trace');\n\t\tcheckbox.setAttribute('value', value.scriptId);\n\t\tcheckbox.checked = autoskip === undefined ? this.autoskip : autoskip;\n\t\tli.append(checkbox);\n\n\t\tlet text = document.createElement('input');\n\t\ttext.setAttribute('type', 'text');\n\t\ttext.setAttribute('readonly', 'true');\n\t\ttext.setAttribute('value', value.url);\n\t\tli.append(text);\n\n\t\tlet button = document.createElement('button');\n\t\tbutton.textContent = 'source';\n\t\tbutton.addEventListener('click', () => {\n\t\t\tcommand('Debugger.getScriptSource', {scriptId: value.scriptId}, (e) => {\n\t\t\t\tlet w = window.open();\n\t\t\t\tw.document.write('<h3>' + (value.url === '' ? 'no url' : value.url) + '</h3><pre>' + e.scriptSource + '</pre>');\n\t\t\t});\n\t\t})\n\t\tli.append(button);\n\n\t\tthis.block.ul.append(li);\n\t},\n\tisset(scriptId) {\n\t\treturn this.list[scriptId] !== undefined;\n\t},\n\tclear() {\n\t\tthis.list = {};\n\t\tthis.block.ul.innerHTML = '';\n\t},\n\tinit() {\n\t\tlet div = document.createElement('div');\n\t\tdiv.textContent = 'js scripts';\n\n\t\tlet label = document.createElement('label');\n\t\tlabel.textContent = 'pause for new';\n\n\t\tthis._pause = document.createElement('input');\n\t\tthis._pause.setAttribute('type', 'checkbox');\n\n\t\tlabel.prepend(this._pause);\n\t\tdiv.append(label);\n\n\t\tlabel = document.createElement('label');\n\t\tlabel.textContent = 'autoskip';\n\n\t\tthis._autoskip = document.createElement('input');\n\t\tthis._autoskip.setAttribute('type', 'checkbox');\n\n\t\tlabel.prepend(this._autoskip);\n\t\tdiv.append(label);\n\n\t\tthis.block.append(div);\n\n\t\tthis.block.ul = document.createElement('ul');\n\t\tthis.block.append(this.block.ul);\n\t}\n};\nscripts.init();\n\n// управление поиском значений\nconst contains = {\n\tblock: document.querySelector('#contains'),\n\tget scope() {\n\t\tlet list = [];\n\t\tthis.block.querySelectorAll('div input[type="checkbox"]:checked').forEach((e) => {\n\t\t\tlist.push(e.value);\n\t\t});\n\t\treturn list;\n\t},\n\tget list() {\n\t\tlet list = {};\n\t\tthis.block.querySelectorAll('li input[type="text"]').forEach((e) => {\n\t\t\tif (e.value !== '' && list[e.value] === undefined) {\n\t\t\t\tlist[e.value] = e.previousSibling.checked;\n\t\t\t}\n\t\t});\n\t\treturn list;\n\t},\n\tsearch(value, name, location) {\n\t\tvalue = value.toString();\n\t\tfor (let search in this.list) {\n\t\t\tif (value.indexOf(search) > -1) {\n\t\t\t\tif (this.list[search]) {\n\t\t\t\t\ttrace.on = false;\n\t\t\t\t\tcommand('Debugger.pause');\n\t\t\t\t}\n\t\t\t\tresult.add(value, name, location);\n\t\t\t}\n\t\t}\n\t},\n\tadd() {\n\t\tlet li = document.createElement('li');\n\n\t\tlet checkbox = document.createElement('input');\n\t\tcheckbox.setAttribute('type', 'checkbox');\n\t\tcheckbox.setAttribute('title', 'pause if contains');\n\t\tli.append(checkbox);\n\n\t\tlet text = document.createElement('input');\n\t\ttext.setAttribute('type', 'text');\n\t\ttext.setAttribute('value', '');\n\t\ttext.setAttribute('placeholder', 'type...');\n\t\ttext.addEventListener('focusout', (e) => {\n\t\t\tif (text.value === '' && li !== this.block.querySelector('li:last-child')) {\n\t\t\t\tli.remove();\n\t\t\t}\n\t\t});\n\t\ttext.addEventListener('keyup', (e) => {\n\t\t\tlet last = this.block.querySelector('li:last-child');\n\t\t\tif (text.value === '') {\n\t\t\t\tif (li === last.previousSibling) {\n\t\t\t\t\tlast.remove();\n\t\t\t\t}\n\t\t\t} else if (last.querySelector('input[type=text]').value !== '') {\n\t\t\t\tthis.add();\n\t\t\t}\n\t\t})\n\t\tli.append(text);\n\n\t\tthis.block.ul.append(li);\n\t},\n\tinit() {\n\t\tlet div = document.createElement('div');\n\t\tdiv.textContent = 'search in scope';\n\n\t\t// global, local, with, closure, catch, block, script, eval, module, wasm-expression-stack\n\t\tlet scopes = ['local', 'script', 'global'];\n\n\t\tfor (let i = 0; i < scopes.length; i++) {\n\t\t\tlet label = document.createElement('label');\n\t\t\tlabel.textContent = scopes[i];\n\n\t\t\tlet checkbox = document.createElement('input');\n\t\t\tcheckbox.setAttribute('type', 'checkbox');\n\t\t\tcheckbox.setAttribute('value', scopes[i]);\n\t\t\tcheckbox.checked = i !== (scopes.length - 1);\n\n\t\t\tlabel.prepend(checkbox);\n\t\t\tdiv.append(label);\n\t\t}\n\n\t\tthis.block.append(div);\n\n\t\tthis.block.ul = document.createElement('ul');\n\t\tthis.block.append(this.block.ul);\n\n\t\tthis.add();\n\t}\n};\ncontains.init();\n\n// отображение результата поиска\nconst result = {\n\tblock: document.querySelector('#result'),\n\tadd(value, name, location) {\n\t\tlet id = [\n\t\t\tvalue,\n\t\t\tscripts.list[location.scriptId].url,\n\t\t\tlocation.lineNumber,\n\t\t\tlocation.columnNumber,\n\t\t];\n\n\t\t// хеш, чтобы не повторяться\n\t\tid = '_' + crc32(id.join('|'));\n\t\tif (this.block.ol.querySelector('#' + id)) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet li = document.createElement('li');\n\t\tli.setAttribute('id', id);\n\n\t\tlet div = document.createElement('div');\n\t\tdiv.textContent = value;\n\t\tli.append(div);\n\n\t\tdiv = document.createElement('div');\n\t\tdiv.textContent = '[' + name.join('.') + ']';\n\t\tli.append(div);\n\n\t\tlet checkbox = document.createElement('input');\n\t\tcheckbox.setAttribute('type', 'checkbox');\n\t\tcheckbox.setAttribute('checked', 'true');\n\t\tcheckbox.setAttribute('disabled', 'true');\n\t\tcheckbox.setAttribute('title', '');\n\t\tli.append(checkbox);\n\n\t\tlet text = document.createElement('input');\n\t\ttext.setAttribute('type', 'text');\n\t\ttext.setAttribute('readonly', 'true');\n\t\ttext.setAttribute('value', scripts.list[location.scriptId].url);\n\t\tli.append(text);\n\n\t\tlet button = document.createElement('button');\n\t\tbutton.setAttribute('title', location.lineNumber + ':' + location.columnNumber);\n\t\tbutton.textContent = 'source';\n\n\t\t// предполагаемое место в исходнике\n\t\tbutton.addEventListener('click', () => {\n\t\t\tcommand('Debugger.getScriptSource', {scriptId: location.scriptId}, (e) => {\n\t\t\t\tif (chrome.runtime.lastError) {\n\t\t\t\t\talert(JSON.stringify(chrome.runtime.lastError));\n\t\t\t\t} else {\n\t\t\t\t\tlet scriptSource = e.scriptSource.split(/[\r\n]/);\n\t\t\t\t\tif (scriptSource[location.lineNumber] === undefined) {\n\t\t\t\t\t\talert('bad location');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tscriptSource.splice(location.lineNumber, 1,\n\t\t\t\t\t\t\tscriptSource[location.lineNumber].substring(0, location.columnNumber),\n\t\t\t\t\t\t\t'<span style="padding: 0 5px; background: #00ff00" id="position">' + location.lineNumber + ':' + location.columnNumber + '</span>' + scriptSource[location.lineNumber].substring(location.columnNumber)\n\t\t\t\t\t\t)\n\t\t\t\t\t\tlet w = window.open();\n\t\t\t\t\t\tw.document.write('<h3>' + (scripts.list[location.scriptId].url === '' ? 'no url' : scripts.list[location.scriptId].url) + '</h3><pre>' + scriptSource.join('\n') + '</pre>');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t})\n\t\tli.append(button);\n\n\t\tthis.block.ol.prepend(li);\n\t},\n\tclear() {\n\t\tthis.list = {};\n\t\tthis.block.ol.innerHTML = '';\n\t},\n\tdisabled() {\n\t\tthis.list = {};\n\t\tthis.block.ol.querySelectorAll('li[id]').forEach((e) => {\n\t\t\te.removeAttribute('id');\n\t\t\te.querySelectorAll('input, button').forEach((e) => {\n\t\t\t\te.disabled = true;\n\t\t\t});\n\t\t});\n\t},\n\tget preserve() {\n\t\treturn this._preserve.checked;\n\t},\n\tinit() {\n\t\tlet div = document.createElement('div');\n\t\tdiv.textContent = 'result';\n\n\t\tlet label = document.createElement('label');\n\t\tlabel.textContent = 'preserve';\n\n\t\tthis._preserve = document.createElement('input');\n\t\tthis._preserve.setAttribute('type', 'checkbox');\n\t\tthis._preserve.checked = true;\n\n\t\tlabel.prepend(this._preserve);\n\t\tdiv.append(label);\n\n\t\tthis.block.append(div);\n\n\t\tthis.block.ol = document.createElement('ol');\n\t\tthis.block.ol.setAttribute('reversed', 'true');\n\t\tthis.block.append(this.block.ol);\n\t}\n};\nresult.init();\n\n// немного минимизации\nconst command = (method, params = {}, callback = null) => {\n\tchrome.debugger.sendCommand(\n\t\t{tabId},\n\t\tmethod,\n\t\tparams,\n\t\tfunction (e) {\n\t\t\tif (chrome.runtime.lastError) {\n\n\t\t\t\t// если scriptId не найден, останавливаем трейсер\n\t\t\t\tif (method === 'Debugger.stepInto') {\n\t\t\t\t\tcommand('Debugger.stepInto',{}, () => {\n\t\t\t\t\t\ttrace.on = false;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (callback) {\n\t\t\t\tcallback(e);\n\t\t\t}\n\t\t}\n\t);\n};\n\n// собственно step into\nconst stepInto = () => {\n\tlet skipList = [];\n\tfor (let i = 0; i < scripts.skip.length; i++) {\n\t\tlet scriptId = scripts.skip[i];\n\t\tskipList.push({\n\t\t\tscriptId: scriptId,\n\t\t\tstart: {lineNumber: scripts.list[scriptId].startLine, columnNumber: scripts.list[scriptId].startColumn},\n\t\t\tend: {lineNumber: scripts.list[scriptId].endLine, columnNumber: scripts.list[scriptId].endColumn},\n\t\t});\n\t}\n\tcommand('Debugger.stepInto',{skipList}, () => {\n\t\tsteps.count++;\n\t});\n};\n\n// данные из object с рекурсией\nconst ignore = Object.keys(window);\nconst getProperties = (object, name, location) => {\n\n\tchrome.debugger.sendCommand(\n\t\t{tabId},\n\t\t'Runtime.getProperties',\n\t\t{objectId: object.objectId},\n\t\t(e) => {\n\t\t\tif (chrome.runtime.lastError) {\n\t\t\t\tconsole.error(object, chrome.runtime.lastError);\n\t\t\t} else if (e !== undefined && e.result.length > 0) {\n\n\t\t\t\te.result.forEach((item) => {\n\t\t\t\t\tif (item.value === undefined || item.value.type === undefined) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (item.symbol !== undefined) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (item.value.type === 'function') {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (object.className !== undefined) {\n\t\t\t\t\t\tif (object.className !== 'Array' && object.className !== 'Object') {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (object.className === 'Window' && ignore.indexOf(item.name) > -1) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (item.value.objectId !== undefined) {\n\t\t\t\t\t\tname = item.name === undefined ? name : name.concat([item.name]);\n\t\t\t\t\t\tgetProperties(item.value, name, location);\n\t\t\t\t\t} else if (item.value.value !== undefined) {\n\t\t\t\t\t\tcontains.search(item.value.value, name, location);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n};\n\n// слушаем дебаггер\nchrome.debugger.onEvent.addListener((source, method, params) => {\n\tif (source.tabId !== tabId) {\n\t\treturn;\n\t}\n\n\tif (method === 'Debugger.scriptParsed') {\n\t\tif (params.executionContextAuxData.type === 'default') {\n\t\t\tif (scripts.pause) {\n\t\t\t\ttrace.on = false;\n\t\t\t}\n\t\t\tscripts.add(params);\n\t\t}\n\t} else if (method === 'Debugger.resumed') {\n\n\t} else if (method === 'Debugger.paused') {\n\n\t\tif (!trace.on) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (Object.keys(contains.list).length > 0) {\n\t\t\tparams.callFrames.forEach((frame) => {\n\t\t\t\tif (!scripts.isset(frame.functionLocation.scriptId)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (frame.returnValue !== undefined) {\n\t\t\t\t\tlet name = ['function' + (frame.functionName === '' ? '' : ' ' + frame.functionName)];\n\t\t\t\t\tif (frame.returnValue.type === 'object') {\n\t\t\t\t\t\tgetProperties(frame.returnValue, name, frame.location);\n\t\t\t\t\t} else if (frame.returnValue.value !== undefined) {\n\t\t\t\t\t\tcontains.search(frame.returnValue.value, name, frame.location);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tframe.scopeChain.forEach((chain) => {\n\t\t\t\t\tif (contains.scope.indexOf(chain.type) === -1) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet name = chain.name === undefined ? [] : [chain.name];\n\t\t\t\t\tgetProperties(chain.object, name, frame.location);\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t\tstepInto();\n\t}\n});\n\nchrome.debugger.onDetach.addListener((source, reason) => {\n\tattach.on = false;\n});\n\nchrome.tabs.onUpdated.addListener((tid, changeInfo, tab) => {\n\tif (changeInfo.status === 'loading' && tid === tabId) {\n\t\tscripts.clear();\n\t\tif (!result.preserve) {\n\t\t\tresult.clear();\n\t\t} else {\n\t\t\tresult.disabled();\n\t\t}\n\t\tif (attach.on) {\n\t\t\tcommand('Debugger.pause');\n\t\t}\n\t}\n});
Полное описание расширения Chrome Debugger Tracing