diff --git a/assets/prism/prism.css b/assets/prism/prism.css new file mode 100644 index 0000000..32a54b6 --- /dev/null +++ b/assets/prism/prism.css @@ -0,0 +1,191 @@ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=clike+javascript&plugins=line-highlight */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +pre[data-line] { + position: relative; + padding: 1em 0 1em 3em; +} + +.line-highlight { + position: absolute; + left: 0; + right: 0; + padding: inherit 0; + margin-top: 1em; /* Same as .prism’s padding-top */ + + background: hsla(24, 20%, 50%,.08); + background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); + + pointer-events: none; + + line-height: inherit; + white-space: pre; +} + + .line-highlight:before, + .line-highlight[data-end]:after { + content: attr(data-start); + position: absolute; + top: .4em; + left: .6em; + min-width: 1em; + padding: 0 .5em; + background-color: hsla(24, 20%, 50%,.4); + color: hsl(24, 20%, 95%); + font: bold 65%/1.5 sans-serif; + text-align: center; + vertical-align: .3em; + border-radius: 999px; + text-shadow: none; + box-shadow: 0 1px white; + } + + .line-highlight[data-end]:after { + content: attr(data-end); + top: auto; + bottom: .4em; + } + +.line-numbers .line-highlight:before, +.line-numbers .line-highlight:after { + content: none; +} + diff --git a/assets/prism/prism.js b/assets/prism/prism.js new file mode 100644 index 0000000..b23dd66 --- /dev/null +++ b/assets/prism/prism.js @@ -0,0 +1,6 @@ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=clike+javascript&plugins=line-highlight */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-([\w-]+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,disableWorkerMessageHandler:_self.Prism&&_self.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(w instanceof s)){if(m&&b!=t.length-1){f.lastIndex=k;var _=f.exec(e);if(!_)break;for(var j=_.index+(d?_[1].length:0),P=_.index+_[0].length,A=b,O=k,x=t.length;x>A&&(P>O||!t[A].type&&!t[A-1].greedy);++A)O+=t[A].length,j>=O&&(++b,k=O);if(t[b]instanceof s)continue;I=A-b,w=e.slice(k,O),_.index-=k}else{f.lastIndex=0;var _=f.exec(w),I=1}if(_){d&&(p=_[1]?_[1].length:0);var j=_.index+p,_=_[0].slice(p),P=j+_.length,N=w.slice(0,j),S=w.slice(P),E=[b,I];N&&(++b,k+=N.length,E.push(N));var C=new s(u,h?n.tokenize(_,h):_,y,_,m);if(E.push(C),S&&E.push(S),Array.prototype.splice.apply(t,E),1!=I&&n.matchGrammar(e,t,a,b,k,!0,u),i)break}else if(i)break}}}}},tokenize:function(e,t){var a=[e],r=t.rest;if(r){for(var l in r)t[l]=r[l];delete t.rest}return n.matchGrammar(e,a,t,0,0,!1),a},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if(e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+l.tag+' class="'+l.classes.join(" ")+'"'+(o?" "+o:"")+">"+l.content+""},!_self.document)return _self.addEventListener?(n.disableWorkerMessageHandler||_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(?:true|false)\b/,"function":/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; +Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},/\b(?:as|async|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/],number:/\b(?:(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+)n?|\d+n|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,"function":/[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*\(|\.(?:apply|bind|call)\()/,operator:/-[-=]?|\+[+=]?|!=?=?|<>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^\/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})\]]))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\([^()]*\)|[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/i,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)[^\s()][^()]*?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/,inside:Prism.languages.javascript},{pattern:/(\(\s*)[^\s()][^()]*?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)[^\s()][^()]*?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z][A-Z\d_]*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,greedy:!0,inside:{interpolation:{pattern:/\${[^}]+}/,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\s\S]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript",greedy:!0}}),Prism.languages.js=Prism.languages.javascript; +!function(){function e(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function t(e,t){return t=" "+t+" ",(" "+e.className+" ").replace(/[\n\t]/g," ").indexOf(t)>-1}function n(e,n,i){n="string"==typeof n?n:e.getAttribute("data-line");for(var o,l=n.replace(/\s+/g,"").split(","),a=+e.getAttribute("data-line-offset")||0,s=r()?parseInt:parseFloat,d=s(getComputedStyle(e).lineHeight),u=t(e,"line-numbers"),c=0;o=l[c++];){var p=o.split("-"),m=+p[0],f=+p[1]||m,h=e.querySelector('.line-highlight[data-range="'+o+'"]')||document.createElement("div");if(h.setAttribute("aria-hidden","true"),h.setAttribute("data-range",o),h.className=(i||"")+" line-highlight",u&&Prism.plugins.lineNumbers){var g=Prism.plugins.lineNumbers.getLine(e,m),y=Prism.plugins.lineNumbers.getLine(e,f);g&&(h.style.top=g.offsetTop+"px"),y&&(h.style.height=y.offsetTop-g.offsetTop+y.offsetHeight+"px")}else h.setAttribute("data-start",m),f>m&&h.setAttribute("data-end",f),h.style.top=(m-a-1)*d+"px",h.textContent=new Array(f-m+2).join(" \n");u?e.appendChild(h):(e.querySelector("code")||e).appendChild(h)}}function i(){var t=location.hash.slice(1);e(".temporary.line-highlight").forEach(function(e){e.parentNode.removeChild(e)});var i=(t.match(/\.([\d,-]+)$/)||[,""])[1];if(i&&!document.getElementById(t)){var r=t.slice(0,t.lastIndexOf(".")),o=document.getElementById(r);o&&(o.hasAttribute("data-line")||o.setAttribute("data-line",""),n(o,i,"temporary "),document.querySelector(".temporary.line-highlight").scrollIntoView())}}if("undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector){var r=function(){var e;return function(){if("undefined"==typeof e){var t=document.createElement("div");t.style.fontSize="13px",t.style.lineHeight="1.5",t.style.padding=0,t.style.border=0,t.innerHTML=" 
 ",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}}(),o=0;Prism.hooks.add("before-sanity-check",function(t){var n=t.element.parentNode,i=n&&n.getAttribute("data-line");if(n&&i&&/pre/i.test(n.nodeName)){var r=0;e(".line-highlight",n).forEach(function(e){r+=e.textContent.length,e.parentNode.removeChild(e)}),r&&/^( \n)+$/.test(t.code.slice(-r))&&(t.code=t.code.slice(0,-r))}}),Prism.hooks.add("complete",function l(e){var r=e.element.parentNode,a=r&&r.getAttribute("data-line");if(r&&a&&/pre/i.test(r.nodeName)){clearTimeout(o);var s=Prism.plugins.lineNumbers,d=e.plugins&&e.plugins.lineNumbers;t(r,"line-numbers")&&s&&!d?Prism.hooks.add("line-numbers",l):(n(r,a),o=setTimeout(i,1))}}),window.addEventListener("hashchange",i),window.addEventListener("resize",function(){var e=document.querySelectorAll("pre[data-line]");Array.prototype.forEach.call(e,function(e){n(e)})})}}(); diff --git a/background.js b/background.js index 6efa963..0b7cec6 100644 --- a/background.js +++ b/background.js @@ -1 +1,2 @@ // Do we really need it? + diff --git a/devtools.js b/devtools.js index 3ad9157..7faf761 100644 --- a/devtools.js +++ b/devtools.js @@ -1,20 +1,29 @@ 'use strict'; // Create a new panel + +const scripts = new Map(); + chrome.devtools.panels.create('SPAudit', null, 'panel.html', - function (panel) { + async function (panel) { const cdt = new ChromeDebuggerDriver(); panel.onShown.addListener(async (panelWindow) => { + cdt.on('Debugger.scriptParsed', (item) => scripts.set(item.scriptId, item)); await cdt.start(); + cdt.sendCommand('Debugger.enable', {}); panelWindow.document.addEventListener(InstructionEvent.TYPE, async (instruction) => { const { data: { command, params } } = instruction; + if (command === 'SPAudit.getScripts') { + instruction.resolve(scripts); + } + cdt.sendCommand(command, params) .then(instruction.resolve) .catch(instruction.reject); @@ -23,6 +32,7 @@ chrome.devtools.panels.create('SPAudit', panel.onHidden.addListener(async () => { - await cdt.stop() + await cdt.stop(); + scripts.clear(); }); }); diff --git a/lib/CD.js b/lib/CD.js index dcdc1cb..550b0c9 100644 --- a/lib/CD.js +++ b/lib/CD.js @@ -14,7 +14,8 @@ const ChromeDebug = { highlightNode: (nodeId) => sendCommand('DOM.highlightNode', { nodeId, highlightConfig: {contentColor: { r: 100, g: 50, b: 50 }}}), // TODO: better querySelectorAll: (nodeId = 1, selector = '*') => sendCommand('DOM.querySelectorAll', { nodeId, selector }), resolveNode: (nodeId) => sendCommand('DOM.resolveNode', { nodeId }), - hideHighlight: () => sendCommand('DOM.hideHighlight') + hideHighlight: () => sendCommand('DOM.hideHighlight'), + focus: (nodeId) => sendCommand('DOM.focus', { nodeId }), }, DOMDebugger: { getEventListeners: (objectId) => sendCommand('DOMDebugger.getEventListeners', { objectId }) @@ -28,6 +29,9 @@ const ChromeDebug = { Debugger: { getScriptSource: (scriptId) => sendCommand('Debugger.getScriptSource', { scriptId }), enable: () => sendCommand('Debugger.enable') + }, + SPAudit: { + getScripts: () => sendCommand('SPAudit.getScripts') } }; diff --git a/lib/CDD.js b/lib/CDD.js index e6e1d83..1e0b2bc 100644 --- a/lib/CDD.js +++ b/lib/CDD.js @@ -2,8 +2,48 @@ const ChromeDebuggerDriver = class { constructor() { + const self = this; this.debuggee = { tabId: chrome.devtools.inspectedWindow.tabId }; this.enabled = false; + chrome.debugger.onEvent.addListener(function (source, method, params) { + + if (source.tabId !== self.debuggee.tabId) { + return; + } + self.dispatch(method, params); + }); + + this.listeners = new Map(); + } + + on(eventName, cb) { + + let line = this.listeners.get(eventName); + if (!line) { + line = new Set(); + this.listeners.set(eventName, line); + } + line.add(cb); + } + + off(eventName, cb) { + + const line = this.listeners.get(eventName); + if (!line) { + return; + } + line.delete(cb); + } + + dispatch(eventName, args) { + + const line = this.listeners.get(eventName); + if (!line) { + return; + } + for (const cb of line) { + cb(args); + } } _lastError() { diff --git a/panel.js b/lib/panel.dom.js similarity index 58% rename from panel.js rename to lib/panel.dom.js index cb8f9b3..e64b780 100644 --- a/panel.js +++ b/lib/panel.dom.js @@ -1,17 +1,4 @@ -const wait = function (t) { - return new Promise((r) => { - setTimeout(r, t); - }); -}; - -const getAllNodeIds = async function (selector = '*') { - - const { root } = await ChromeDebug.DOM.getDocument(); - const rootId = root.nodeId; - const { nodeIds } = await ChromeDebug.DOM.querySelectorAll(root.nodeId, selector); - return [rootId].concat(nodeIds); -}; - +'use strict'; async function renderNodeDetails (nodeId, objectId, listeners) { const main = document.createElement('div'); @@ -65,13 +52,6 @@ async function renderNode(nodeId, objectId, listeners) { div.appendChild(content); } -const cleanup = function (node) { - - while (node.firstChild) { - node.removeChild(node.firstChild); - } -}; - async function filterNodes() { // TODO: loading bar @@ -95,47 +75,6 @@ async function filterNodes() { } } } -async function act() { - - await ChromeDebug.DOM.enable(); - await ChromeDebug.Debugger.enable(); - - const allNodes = await getAllNodeIds(); - - console.log(allNodes); -/* for (const nodeId of allNodes.nodeIds) { - const { object: { objectId } } = await ChromeDebug.DOM.resolveNode(nodeId); // should always be an html element - console.log({ nodeId, objectId }); - const { listeners } = await ChromeDebug.DOMDebugger.getEventListeners(objectId); - console.log(listeners); - for (const listener of listeners) { - const { scriptId, lineNumber, columnNumber, type } = listener; - const { scriptSource } = await ChromeDebug.Debugger.getScriptSource(scriptId); - console.log(scriptSource); - console.log(acorn.parse(scriptSource)); - } - }*/ -} - -/*let isCov = false; -async function cov() { - if (isCov) { - await ChromeDebug.Debugger.enable(); - isCov = false; - const { result } = await ChromeDebug.Profiler.takePreciseCoverage(); - await ChromeDebug.Profiler.stopPreciseCoverage(); - for (const line of result) { - const { scriptId } = line; - const script = await ChromeDebug.Debugger.getScriptSource(scriptId); - console.log(line, script); - } - return; - } - await ChromeDebug.Profiler.enable(); - await ChromeDebug.Profiler.startPreciseCoverage(); - isCov = true; -}*/ - document.getElementById('filterBtn') .addEventListener('click', filterNodes); diff --git a/lib/panel.js.js b/lib/panel.js.js new file mode 100644 index 0000000..6309ec9 --- /dev/null +++ b/lib/panel.js.js @@ -0,0 +1,78 @@ +'use strict'; + +function isEvalCall(node) { + return (node.type === 'CallExpression') && + (node.callee.type === 'Identifier') && + (node.callee.name === 'eval') ; +} + +function isInnerHTMLCall(node) { + return (node.type === 'AssignmentExpression') && + node.left.property && + (node.left.property.type === 'Identifier') && + (node.left.property.name === 'innerHTML'); +} + +Prism.hooks.add('before-highlight', function (env) { + env.code = env.element.innerText; +}); + +const displayScript = function (scriptSource, scriptData, highlightLines) { + + const toggleId = 'script-' + scriptData.scriptId; + const main = document.getElementById('accordionJS'); + const card = createElement('div', { class: 'card' }); + const cardHeader = createElement('div', { class: 'card-header', id: 'head-' + scriptData.scriptId }); + card.appendChild(cardHeader); + const cardTitle = createElement('h2', { class: 'mb-0' }); + cardHeader.appendChild(cardTitle); + + const btn = createElement('button', { class: 'btn btn-link', type: 'button', 'data-toggle': 'collapse', 'data-target': '#' + toggleId, 'aria-controls': toggleId }); + btn.innerText = 'script' + scriptData.scriptId + ' ' + scriptData.url; + cardTitle.appendChild(btn); + if (highlightLines.length > 0) { + const span = createElement('span', {class: 'badge badge-secondary'}); + span.innerText = highlightLines.length + ' issues'; + cardTitle.appendChild(span); + } + + + const content = createElement('div', { class: 'collapse', id: toggleId, 'aria-labelledby': toggleId, 'data-parent': '#accordionJS' }); + card.appendChild(content); + const body = createElement('div', { class: 'card-body' }); + content.appendChild(body); + + const pre = createElement('pre', { 'data-line': highlightLines.join(',') }); + body.appendChild(pre); + const code = createElement('code', { 'class': 'language-js' }); + pre.appendChild(code); + code.innerText = scriptSource; + main.appendChild(card); +}; + + +// TODO: add support for failed to parse scripts too. +async function scanJS() { + + cleanup(document.getElementById('accordionJS')); + const scripts = await ChromeDebug.SPAudit.getScripts(); + for (const scriptData of scripts.values()) { + const { scriptSource } = await ChromeDebug.Debugger.getScriptSource(scriptData.scriptId); + const parse = scriptData.isModule ? esprima.parseModule : esprima.parseScript; + const highlights = []; + parse(scriptSource, {}, function (node, meta) { + if (isEvalCall(node)) { + highlights.push(meta.start.line); + } + if (isInnerHTMLCall(node)) { + highlights.push(meta.start.line); + } + }); + displayScript(scriptSource, scriptData, highlights); + } + Prism.highlightAll(); +} +document.getElementById('scanJS') + .addEventListener('click', scanJS); + + diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 0000000..24a68cc --- /dev/null +++ b/lib/util.js @@ -0,0 +1,26 @@ +'use strict'; + +const getAllNodeIds = async function (selector = '*') { + + const { root } = await ChromeDebug.DOM.getDocument(); + const { nodeIds } = await ChromeDebug.DOM.querySelectorAll(root.nodeId, selector); + return nodeIds; +}; + +const cleanup = function (node) { + + while (node.firstChild) { + node.removeChild(node.firstChild); + } +}; + +const createElement = function (tag, attributes) { + + const element = document.createElement(tag); + Object.keys(attributes) + .forEach((key) => { + + element.setAttribute(key, attributes[key]); + }); + return element; +}; diff --git a/manifest.json b/manifest.json index 9771b56..af9d0de 100644 --- a/manifest.json +++ b/manifest.json @@ -8,7 +8,7 @@ ] }, "devtools_page": "devtools.html", - "content_security_policy": "script-src 'self' https://cdnjs.cloudflare.com/ajax/libs/acorn/6.0.7/acorn.js https://code.jquery.com/jquery-3.3.1.slim.min.js https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js 'unsafe-eval'; object-src 'self'", + "content_security_policy": "script-src 'self' https://cdnjs.cloudflare.com/ajax/libs/acorn/6.0.7/acorn.js https://code.jquery.com/jquery-3.3.1.slim.min.js https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js https://unpkg.com/esprima@~4.0/dist/esprima.js 'unsafe-eval'; object-src 'self'", "permissions": [ "", "webNavigation", diff --git a/panel.html b/panel.html index 0ce858f..4850ce2 100644 --- a/panel.html +++ b/panel.html @@ -5,8 +5,12 @@ + + + +
@@ -26,7 +33,6 @@
-
@@ -39,11 +45,19 @@
-
JS
- - +
+ +
+
+
+
+ analysisMode +
+ - + + + diff --git a/tests/index.html b/tests/index.html index b4daa69..06fee55 100644 --- a/tests/index.html +++ b/tests/index.html @@ -10,6 +10,7 @@
+