let nowFileName = ""; let jsonInfo = {}; let tabId = ""; let nodeObj; let jslist; let cssList; let cssFileInfo = {} let oldFile; // 记录上一次点击的文件信息 let path; let targe; // 记录页卡信息 let pageTab; chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { if (request.msgToPopup === "发送页面") { // 结构接收的信息 let { jslist: _jslist, targe: _targe, csslist: _csslist, tabId: _tabId, path: _path } = request.data; jslist = _jslist; tabId = _tabId; cssList = _csslist; // 页面的所有样式信息 path = _path; targe = _targe; cssFileInfo = {} await attach(); await getPathNodeInfo(path); if (!pageTab) { // 创建页卡实例 let tabs = [ new Tab({ id: 'tabFirst', isAlived: true, activeEvent: () => { domOptionTab() } }), new Tab({ id: 'tabSecond', isAlived: true, activeEvent: () => { domPropTab() } }), new Tab({ id: 'tabThree', isAlived: true, activeEvent: () => { domEventTab() } }) ] pageTab = new PageTab({ tabs }); } pageTab.reset(); let activeTab = pageTab.getActiveTab(); if (activeTab) { pageTab.activeTabById(activeTab.id); } else { pageTab.activeTabById("tabFirst"); } } }); 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 cssCodeToAST(cssCode) { let curAst = csstree.parse(cssCode, { parseAtrulePrelude: false, parseRulePrelude: false, parseValue: false, positions: true, }); let selectors = []; //https://github.com/csstree/csstree/blob/master/docs/parsing.md#parsesource-options await csstree.walk(curAst, (node) => { if (node.type === 'Rule')//node.type === 'Declaration' { let curSelector; if (node.prelude.children) { curSelector = node.prelude.children.map(child => child.raw); } else { curSelector = node.prelude.value; } let loc = node.prelude.loc; if (curSelector) selectors.push({ //name: curSelector,//curSelector, name: curSelector.replaceAll("\r\n*", " ").replaceAll("\r\n", " "), sourceName: cssCode.substring(loc.start.offset, loc.end.offset), loc: loc, }); } }); return selectors; } 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; 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, 分类: "伪类样式" }) }) }); 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 text = rule.selectorList.text; let obj = { origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, text) : '用户自定义', text, style }; result.匹配的样式.push(obj) }) // 解析伪类的样式 pseudoElements.forEach(cssRule => { let { pseudoType, matches } = cssRule; let obj = { pseudoType, matchs: matches.map(({ rule }) => { let text = rule.selectorList.text; return { origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, text) : '用户自定义', 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; let text = rule.selectorList.text; result.继承的样式.push({ origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, text) : '用户自定义', text, style }) }) }) return result; } } /** * 页卡组件 */ class PageTab { #tabs = [] #tabContainer constructor({ tabs }) { this.#tabs = tabs || []; this.#tabContainer = document.querySelector('.tabs'); this.#initClickEvent(); } addTab(tabInfo) { this.#tabs.push(tabInfo) } #initClickEvent() { this.#tabContainer.addEventListener('click', (e) => { let target = e.target; if (target.classList.contains("tab")) { this.activeTabById(target.dataset.id); } }) } get tabs() { return this.#tabs; } activeTabById(id) { // 移除已经选中的 let activedTab = this.tabs.filter(a => a.isActive); if (activedTab.length > 0) { activedTab.forEach(a => { if (a.id !== id) { a.removeActive(); } }) } // 给设置的选中 let awaitActiveTab = this.tabs.find(a => a.id === id); if (awaitActiveTab) { awaitActiveTab.addActive(); } } reset() { this.tabs.forEach(s => { s.changeLived(false) }) } getActiveTab() { return this.tabs.find(a => a.isActive) } } class Tab { #id // 页卡id #tabEle // tab页卡元素 #contentEle // tab内容元素 #isAlived // 是否保持持久化(为true表示 页卡激活事件只执行一次,当前内容会一直存在,如果为false,则表示每次点击页卡都会重新执行激活事件) #event // 激活页卡执行事件 #isLived get id() { return this.#id; } constructor({ id, isAlived, activeEvent }) { this.#id = id; this.#tabEle = Tab.getTabEleById(id); this.#contentEle = Tab.getTabContentById(id); this.#isAlived = isAlived; this.#event = activeEvent; } get tabInfo() { return { isActive: this.isActive, id: this.#id } } /** * 判断是否为选中的标签 */ get isActive() { return this.#tabEle.classList.contains('active'); } /** * 添加选中效果 */ addActive() { this.#tabEle.classList.add('active'); this.#contentEle.classList.add('active'); this.activeEvent(); this.#isLived = true; } activeEvent() { if (this.#isAlived === true && this.#isLived === true) { // 如果开启了持久化,表示只执行一次 return } typeof this.#event === 'function' && this.#event(); } changeLived(val) { this.#isLived = val } /** * 移除选中效果 */ removeActive() { this.#tabEle.classList.remove('active'); this.#contentEle.classList.remove('active'); } static getTabEleById(id) { return document.querySelector(`.tabs .tab[data-id="${id}"]`); } static getTabContentById(id) { return document.querySelector(`.tab-container #${id}`); } } /** * 执行DOM操作页卡事件 */ async function domOptionTab() { //初始化JS代码编辑器 let jsCode = new CodeEditor(); jsCode.InitEditor($("#jscode")[0], false, false, [], false); jsCode.dataType = 3; // 初始化表格 let TabInstance = $("#filterTable") .dxDataGrid({ dataSource: [], keyExpr: "resource_detail_id", remoteOperations: false, searchPanel: { visible: true, highlightCaseSensitive: true, }, groupPanel: { visible: false }, grouping: { allowCollapsing: true, autoExpandAll: true, expandMode: "rowClick", }, selection: { mode: "single", }, allowColumnReordering: true, rowAlternationEnabled: false, noDataText: "无数据", showBorders: true, height: "100%", columns: [ { dataField: "JS文件", dataType: "JS文件", groupIndex: 0 }, { dataField: "DOM操作分类", dataType: "DOM操作分类", }, { dataField: "过滤器", caption: "过滤器", }, { dataField: "所在位置", dataType: "所在位置", }, ], onContentReady(e) { }, onSelectionChanged: function (selectedItems) { let data = selectedItems.selectedRowsData[0]; if (data) { oldFile = ""; 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"); 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; } } }); TabInstance.option({ dataSource: data, }); } /** * 执行DOM属性页卡事件 */ async function domPropTab() { let cssCode = new CodeEditor(); cssCode.InitEditor($("#csscode")[0], false, false, [], false); cssCode.dataType = 2; let CSSTabInstance = $("#cssJson") .dxDataGrid({ dataSource: [], keyExpr: "id", remoteOperations: false, searchPanel: { visible: true, highlightCaseSensitive: true, }, selection: { mode: "single", }, groupPanel: { visible: false }, grouping: { allowCollapsing: true, autoExpandAll: true, expandMode: "rowClick", }, allowColumnReordering: true, rowAlternationEnabled: false, showBorders: true, height: "100%", paging: { enabled: false, pageSize: 0, }, columns: [ { dataField: "文件", caption: "来源文件", groupCellTemplate: function (tdom, tdMsg) { let name = tdMsg.value; try { if (/自定义样式/.test(name)) { name = name.match(/自定义样式/)[0] } else if (/\/([^\/]+\.css)(\?[^ ]*)?$/.test(name)) { name = name.match(/\/([^\/]+\.css)(\?[^ ]*)?$/)[1]; } } catch { } tdom.text(name) }, groupIndex: 0 }, { dataField: "分类", caption: "分类" }, { dataField: "选择器", caption: "选择器" }, { dataField: "样式属性", caption: "样式属性", cellTemplate: function (tdom, tdMsg) { let styles = tdMsg.value; let html = styles.map(({ name, value }) => `