let nowFileName = ""; let jsonInfo = {}; let tabId = ""; let nodeObj; let jslist; let cssList; let cssFileInfo = {} chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { if (request.msgToPopup === "发送页面") { cssFileInfo = {} //初始化JS代码编辑器 window.jsCode = new CodeEditor(); jsCode.InitEditor($("#jscode")[0], false, false, [], false); jsCode.dataType = 3; InitTable(); // 结构接收的信息 let { jslist: _jslist, targe, csslist, tabId: _tabId, path } = request.data; jslist = _jslist; tabId = _tabId; cssList = csslist; // 页面的所有样式信息 parseCodeToAST( jslist.map((item) => { return { code: item.data, name: item.fileName, }; }) ); let data = window.findSelector(); let id = $(targe).attr("id"); let classList = $(targe).attr("class").split(" "); data = data.filter((m) => { let v = m.过滤器; if (v.includes(id)) return true; if (classList.length > 0) { for (const classitem of classList) { if (v.includes(classitem)) return true; } } }); await attach(); await getPathNodeInfo(path); let events = await findEvent(); TabInstance.option({ dataSource: data, }); let cssJsonData = CssJsonData(jsonInfo) CSSTabInstance.option({ dataSource: cssJsonData, }); eventTableInstance.option({ dataSource: translateEventData(events), }); } }); function InitTable() { window.TabInstance = $("#filterTable") .dxDataGrid({ dataSource: [], keyExpr: "resource_detail_id", remoteOperations: false, searchPanel: { visible: true, highlightCaseSensitive: true, }, groupPanel: { visible: true }, grouping: { allowCollapsing: true, autoExpandAll: true, expandMode: "rowClick", }, selection: { mode: "single", }, allowColumnReordering: true, rowAlternationEnabled: true, showBorders: true, height: "100%", columns: [ { dataField: "JS文件", dataType: "JS文件", groupIndex: 0, }, { dataField: "过滤器", caption: "过滤器", }, { dataField: "选择器类型", dataType: "选择器类型", }, { dataField: "调用方式", dataType: "调用方式", }, { dataField: "所在位置", dataType: "所在位置", }, { dataField: "DOM操作分类", dataType: "DOM操作分类", }, ], onContentReady(e) { }, onSelectionChanged: function (selectedItems) { let data = selectedItems.selectedRowsData[0]; jsCode.dataType = 3; if (data) { let ast = data.astJSON; if (data.JS文件 !== nowFileName) { nowFileName = data.JS文件; let code = window.fileList.find((o) => o.name === data.JS文件).code; //更改JS代码 jsCode.SetCode(code); } //jscode定位 let { defineNode: { iid, loc: { end, start }, }, } = data.source_data; { //移出所有高亮 jsCode.RemoveAllHighlight(); //设置高亮 jsCode.CreateHighlight({ startLine: start?.line, startColumn: start?.column + 1, endLine: end?.line, endColumn: end?.column + 1, }); } } }, }) .dxDataGrid("instance"); window.CSSTabInstance = $("#cssJson") .dxDataGrid({ dataSource: [], keyExpr: "id", remoteOperations: false, searchPanel: { visible: true, highlightCaseSensitive: true, }, selection: { mode: "single", }, groupPanel: { visible: true }, grouping: { allowCollapsing: true, autoExpandAll: true, expandMode: "rowClick", }, allowColumnReordering: true, rowAlternationEnabled: true, showBorders: true, height: "100%", paging: { enabled: false, pageSize: 0, }, columns: [ { dataField: "文件", caption: "来源文件", cellTemplate: function (tdom, tdMsg) { let name = tdMsg.value; tdom.text(name === "用户自定义" ? name : name.match(/\/([^\/?]+)(?:\?[^\s]*)?$/)[1]) } }, { dataField: "选择器", caption: "选择器" }, { dataField: "分类", caption: "分类", groupIndex: 0 }, { dataField: "样式属性", caption: "样式属性", cellTemplate: function (tdom, tdMsg) { let styles = tdMsg.value; let html = styles.map(({ name, value }) => `
  • ${name}: ${value}
  • `).join('') tdom.html(``) } }, ], onSelectionChanged: async function (selectedItems) { let data = selectedItems.selectedRowsData[0]; jsCode.dataType = 1; if (data) { if (data.文件) { let fileName = data.文件; let code = "" if (!cssFileInfo[fileName]) { code = fileName === "用户自定义" ? "" : await (await fetch(fileName)).text(); } else { code = !cssFileInfo[fileName] } cssFileInfo[fileName] = code //更改JS代码 jsCode.SetCode(code); } } }, }) .dxDataGrid("instance"); window.eventTableInstance = $("#eventTable") .dxDataGrid({ dataSource: [], keyExpr: "id", remoteOperations: false, searchPanel: { visible: true, highlightCaseSensitive: true, }, selection: { mode: "single", }, allowColumnReordering: true, rowAlternationEnabled: true, showBorders: true, paging: { enabled: false, pageSize: 0, }, height: "100%", columns: [ { dataField: "事件名称", caption: "事件名称", }, { dataField: "绑定对象", caption: "绑定对象", }, { dataField: "事件文件", caption: "事件文件", }, ], }) .dxDataGrid("instance"); } function findPropRef(key, json) { let matchedCSSRules = json.matchedCSSRules; let retArr = []; for (const matchedCSSRule of matchedCSSRules) { let cssProperties = matchedCSSRule.rule.style.cssProperties; let cssProp = cssProperties.find((m) => m.name == key); if (!cssProp) continue; let ret = { 来源: "CSS", 选择器: matchedCSSRule.rule.selectorList.text, 选中选择器: matchedCSSRule.matchingSelectors[0], 属性值: cssProp.value, }; retArr.push(ret); } return retArr; } async function getPathNodeInfo(path) { const doc = await getDocument(); // const htmlInfo = doc.children.find((m) => m.localName === "html"); let info = doc; for (const index of path) { info = getpath(info, index); } nodeObj = info; let nodeId = info.nodeId; let cssInfo = await getMatchedStylesForNode(nodeId); jsonInfo = cssInfo; function getpath(info, index) { let children = info.children; return children[index]; } } async function getMatchedStylesForNode(nodeId) { let ret = await chrome.debugger.sendCommand( { tabId }, "CSS.getMatchedStylesForNode", { nodeId, } ); return ret; } async function getDocument() { const doc = await chrome.debugger.sendCommand({ tabId }, "DOM.getDocument", { pierce: true, depth: -1, }); return doc.root; } async function attach() { // const targets = await chrome.debugger.getTargets(); // let target = targets.find((m) => m.tabId == tabId); // if (target?.attached === true) { // } else { // await chrome.debugger.attach({ tabId }, "1.3"); // } await chrome.debugger.attach({ tabId }, "1.3").catch(() => { }); await chrome.debugger.sendCommand({ tabId }, "DOM.enable"); await chrome.debugger.sendCommand({ tabId }, "CSS.enable"); await chrome.debugger.sendCommand({ tabId }, "Debugger.enable"); } async function findEvent() { let winObj = await chrome.debugger.sendCommand( { tabId }, "Runtime.evaluate", { expression: "self", objectGroup: "", includeCommandLineAPI: false, silent: true, returnByValue: false, generatePreview: false, userGesture: false, awaitPromise: false, } ); let winEvents = await chrome.debugger.sendCommand( { tabId }, "DOMDebugger.getEventListeners", { objectId: winObj.result.objectId, } ); let documentObj = await chrome.debugger.sendCommand( { tabId }, "Runtime.evaluate", { expression: "document", objectGroup: "", includeCommandLineAPI: false, silent: true, returnByValue: false, generatePreview: false, userGesture: false, awaitPromise: false, } ); let documentEvents = await chrome.debugger.sendCommand( { tabId }, "DOMDebugger.getEventListeners", { objectId: documentObj.result.objectId, } ); let selfObj = await chrome.debugger.sendCommand( { tabId }, "DOM.resolveNode", { nodeId: nodeObj.nodeId, } ); let selfEvents = await chrome.debugger.sendCommand( { tabId }, "DOMDebugger.getEventListeners", { objectId: selfObj.object.objectId, } ); for (const listener of winEvents.listeners) { await listenerGetFile(listener); } for (const listener of documentEvents.listeners) { await listenerGetFile(listener); } for (const listener of selfEvents.listeners) { await listenerGetFile(listener); } return { winEvents: winEvents.listeners, documentEvents: documentEvents.listeners, selfEvents: selfEvents.listeners, }; } async function listenerGetFile(listener) { let ret = await chrome.debugger.sendCommand( { tabId }, "Debugger.getScriptSource", { scriptId: listener.scriptId, } ); let js = jslist.find((m) => m.data == ret.scriptSource); if (js) { js.scriptId = listener.scriptId; listener.fileName = js.fileName; listener.fileUrl = js.url; listener.fileData = js.data; } } function translateEventData(eventObj) { let { winEvents, documentEvents, selfEvents } = eventObj; let result = []; result.push( ...winEvents.map((even) => ({ 事件名称: even.type, 绑定对象: "window", 事件文件: even.fileName, })) ); result.push( ...documentEvents.map((even) => ({ 事件名称: even.type, 绑定对象: "document", 事件文件: even.fileName, })) ); result.push( ...selfEvents.map((even) => ({ 事件名称: even.type, 绑定对象: "selft", 事件文件: even.fileName, })) ); return result.map((a, index) => ({ ...a, id: index })); } window.onload = function () { InitTabOperation(); }; /** * 设置操作页卡功能 */ function InitTabOperation() { document.querySelector(".tabs").addEventListener("click", function (e) { let target = e.target; if (target.classList.contains("tab")) { showContent(target); } }); } function showContent(tab) { let id = tab.dataset.id; // 获取点击的页卡ID // 获取选中的页卡元素 let activeTab = document.querySelector(".tabs .active"); if (activeTab !== null) { if (id === activeTab.dataset.id) return; activeTab.classList.remove("active"); } // 获取选中的容器的元素 let activeContent = document.querySelector(".tab-container .active"); if (activeContent !== null) { if (activeContent.id === id) return; activeContent.classList.remove("active"); } // 给新选中的页卡加入选中效果 tab.classList.add("active"); let curActiveContent = document.querySelector(`.tab-container #${id}`); if (curActiveContent !== null) { curActiveContent.classList.add("active"); } } /** * 接收转换后有用的json数据 * @param {} jsonData */ function CssJsonData(jsonData) { let data = CssJsonRules.parseJson(jsonData); let { 伪类的样式, 内敛的样式, 匹配的样式, 继承的样式 } = data; let result = []; 伪类的样式.forEach((rule) => { let { matchs, pseudoType } = rule; matchs.forEach(match => { result.push({ 文件: match.origin, 样式属性: match.style, 选择器: match.text, 分类: "伪类::" + pseudoType + "样式" }) }) }); if (内敛的样式.length > 0) { result.push({ 文件: "", 样式属性: 内敛的样式, 选择器: "", 分类: "内敛样式" }) } 匹配的样式.forEach((rule) => { result.push({ 文件: rule.origin, 样式属性: rule.style, 选择器: rule.text, 分类: "匹配样式" }) }) 继承的样式.forEach((rule) => { result.push({ 文件: rule.origin, 样式属性: rule.style, 选择器: rule.text, 分类: "继承样式" }) }) return result.map((a, index) => ({ ...a, id: index })); } /** * css基础权重规则 */ class CSSSRule { static calculateSpecificity(selector) { // 初始化权重 let ids = 0; let classes = 0; let tags = 0; // 去掉多余的空格 const parts = selector.trim().split(/\s+/); parts.forEach(part => { // 检查每个选择器部分 if (part.startsWith('#')) { ids += 1; // ID选择器 } else if (part.startsWith('.')) { classes += 1; // 类选择器 } else { tags += 1; // 标签选择器 } }); // 返回权重对象 // 计算最终权重值 const specificityValue = (ids * 100) + (classes * 10) + tags; return specificityValue; } static cacheStyle = {} static getStyleHref(styleId, selectorText) { if (CSSSRule.cacheStyle[styleId]) return CSSSRule.cacheStyle[styleId]; foo: for (let sheet of cssList) { try { let { href, selectorTexts } = sheet; for (let text of selectorTexts) if (selectorText === text) { CSSSRule.cacheStyle[styleId] = href; break foo; } } catch { } } return CSSSRule.cacheStyle[styleId] } // css属性扩展 static cssExtent = [ { "property": "margin", "sub-properties": [ "margin-top", "margin-right", "margin-bottom", "margin-left" ] }, { "property": "padding", "sub-properties": [ "padding-top", "padding-right", "padding-bottom", "padding-left" ] }, { "property": "border", "sub-properties": [ "border-width", "border-style", "border-color", "border-top", "border-right", "border-bottom", "border-left" ] }, { "property": "border-style", "sub-properties": [ "border-top-style", "border-right-style", "border-bottom-style", "border-left-style" ] }, { "property": "border-top", "sub-properties": [ "border-top-width", "border-top-style", "border-top-color" ] }, { "property": "border-bottom", "sub-properties": [ "border-bottom-width", "border-bottom-style", "border-bottom-color" ] }, { "property": "border-left", "sub-properties": [ "border-left-width", "border-left-style", "border-left-color" ] }, { "property": "border-right", "sub-properties": [ "border-right-width", "border-right-style", "border-right-color" ] }, { "property": "border-radius", "sub-properties": [ "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius" ] }, { "property": "background", "sub-properties": [ "background-color", "background-image", "background-repeat", "background-position", "background-size", "background-attachment", "background-clip", "background-origin" ] }, { "property": "font", "sub-properties": [ "font-style", "font-variant", "font-weight", "font-size", "line-height", "font-family" ] }, { "property": "list-style", "sub-properties": [ "list-style-type", "list-style-position", "list-style-image" ] }, { "property": "text-decoration", "sub-properties": [ "text-decoration-line", "text-decoration-color", "text-decoration-style", "text-decoration-thickness" ] }, { "property": "transition", "sub-properties": [ "transition-property", "transition-duration", "transition-timing-function", "transition-delay" ] }, { "property": "animation", "sub-properties": [ "animation-name", "animation-duration", "animation-timing-function", "animation-delay", "animation-iteration-count", "animation-direction", "animation-fill-mode", "animation-play-state" ] }, { "property": "grid", "sub-properties": [ "grid-template-rows", "grid-template-columns", "grid-template-areas", "grid-area", "grid-column", "grid-row", "grid-auto-rows", "grid-auto-columns", "grid-auto-flow" ] }, { "property": "flex", "sub-properties": [ "flex-grow", "flex-shrink", "flex-basis" ] }, { "property": "outline", "sub-properties": [ "outline-width", "outline-style", "outline-color" ] } ] } /** * 根据json导出数据 */ class CssJsonRules { static findExtentInfo(entries) { return entries.map(e => { let extentInfo = CSSSRule.cssExtent.find(a => a.property === e.name); if (extentInfo) { return extentInfo["sub-properties"]; } return undefined }).filter(Boolean).flat(); } /** * 解析json数据 * @param {*} jsonData * @returns */ static parseJson(jsonData) { let { inlineStyle, matchedCSSRules, pseudoElements, inherited } = jsonData; let result = { "内敛的样式": [], "匹配的样式": [], "伪类的样式": [], "继承的样式": [] } // 解析json中的内敛样式 result.内敛的样式.push(...inlineStyle.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value }))); // 解析json中的匹配样式 matchedCSSRules.forEach(cssRule => { let { rule } = cssRule; let { shorthandEntries } = rule; // 按照规律查询出信息 let style = rule.style.cssProperties.filter(a => a.text !== undefined || rule.origin === "user-agent").map(({ name, value }) => ({ name, value })); if (rule.origin === "user-agent" && shorthandEntries && shorthandEntries.length > 0) { let sameInfo = CssJsonRules.findExtentInfo(shorthandEntries); style = style.filter(s => !sameInfo.includes(s.name)) } let obj = { origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, rule.selectorList.text) : '用户自定义', text: rule.selectorList.text, style }; result.匹配的样式.push(obj) }) // 解析伪类的样式 pseudoElements.forEach(cssRule => { let { pseudoType, matches } = cssRule; let obj = { pseudoType, matchs: matches.map(({ rule }) => ({ origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, rule.selectorList.text) : '用户自定义', text: rule.selectorList.text, style: rule.style.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value })) })) }; result.伪类的样式.push(obj) }) // 解析继承的样式 inherited.forEach(inheritedCssRule => { let matchedCSSRules = inheritedCssRule.matchedCSSRules; matchedCSSRules.forEach((cssRule) => { let { rule } = cssRule; let style = rule.style.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value })); if (style.length === 0) return; result.继承的样式.push({ origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, rule.selectorList.text) : '用户自定义', text: rule.selectorList.text, style }) }) }) return result; } }