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;
}
}