123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- class CssPriority {
- static _TOKEN_ID = /(#[\w-]+)/g
- static _TOKEN_CLASS = /(\.[\w-]+)/g
- static _TOKEN_ATTR = /(\[[^[\]]+])/g
- static _TOKEN_PSEUDO_CLASS = /(:[\w-]+)/g
- static _TOKEN_PSEUDO_ELEM = /(::[\w-]+)/g
- static _TOKEN_ELEM = /([\w-]+)/g
- static _PSEUDO_ELEMS = [
- 'first-letter',
- 'last-letter',
- 'first-line',
- 'last-line',
- 'first-child',
- 'last-child',
- 'before',
- 'after'
- ]
- static _SYMBOL_B = '\x02'
- static _SYMBOL_C = '\x03'
- static _SYMBOL_D = '\x04'
- static compare(a, b) {
- //在sort应用中,返回<0,则a在b之前,>0则a在b之后
- for (let i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) {
- //return a[i] - b[i]
- return b[i] - a[i];
- }
- }
- return 0
- }
- /**
- * parse then priority of selector (CSS Rule)
- * @param {String} selector css selector
- * @param {Object} opts
- * - {Boolean} opts.important is current rule important
- * - {Number} opts.line line number of css rule
- * @return {Array} array that contains 6 priority number
- */
- static parse(selector, opts) {
- opts = opts || {}
- // priority: [ important, style, R1, R2, R3, line ]
- let important = opts.important ? 1 : 0
- let line = opts.line || 0
- let priority = [important, 0, 0, 0, 0, line]
- selector = selector.replace(/(::?)([\w-]+)/g, (total, left, elem) => {
- if (CssPriority._PSEUDO_ELEMS.indexOf(elem) >= 0) {
- if (left === ':') {
- return '::' + elem
- } else {
- return total
- }
- } else {
- return total
- }
- })
- // replace with symbols
- selector = selector
- .replace(CssPriority._TOKEN_ATTR, CssPriority._SYMBOL_C)
- .replace(CssPriority._TOKEN_PSEUDO_ELEM, CssPriority._SYMBOL_D)
- .replace(CssPriority._TOKEN_PSEUDO_CLASS, CssPriority._SYMBOL_C)
- .replace(CssPriority._TOKEN_ID, CssPriority._SYMBOL_B)
- .replace(CssPriority._TOKEN_CLASS, CssPriority._SYMBOL_C)
- .replace(CssPriority._TOKEN_ELEM, CssPriority._SYMBOL_D)
- // count
- selector = selector.replace(/[\2\3\4]/g, (symbol) => {
- let idx = symbol.charCodeAt(0)
- priority[idx]++
- return '<' + idx + '>'
- })
- return priority
- }
-
- }
- class Lock {
- constructor() {
- this.promise = new Promise((resolve, reject) => {
- this.resolve = resolve;
- this.reject = reject;
- });
- }
- async wait() {
- try {
- let result = await this.promise;
- return result;
- } catch (error) {
- throw error;
- }
- }
- }
- class CssKind {
- static inline = 1;
- static match = 2;
- static parent = 3;
- }
- class CssRefers {
- _document;
- _cssTree;
- _cssAst = [];
- _inheritStyles = new Map([["color", null], ["font", null], ["font-family", null], ["font-size", null], ["font-style", null], ["font-weight", null], ["font-height", null], ["font-variant", null], ["font-stretch", null], ["font-size-adjust", null],
- ["letter-spacing", null], ["word-spacing", null], ["text-align", null], ["text-indent", null], ["text-decoration", null], ["white-space", null], ["text-shadow", null], ["text-transform", null], ["direction", null],
- ["caption-side", null], ["empty-cells", null], ["border-collapse", null],
- ["unicode-bidi", null], ["visibility", null], ["cursor", null], ["user-select", null], ["list-style", null], ["list-style-type", null], ["list-style-position", null], ["list-style-image", null], ["display", null], ["columns", null], ["column-count", null], ["column-gap", null], ["column-rule", null], ["column-span", null], ["column-width", null],
- ["break-before", null], ["break-after", null], ["break-inside", null], ["line-height", null], ["cursor", null], ["quotes", null]
- ]);
- _cssCount = 0;
- constructor(doc, cssTree) {
- this._document = doc;
- this._cssTree = cssTree;
- }
- async #initCssAst() {
- if (this._cssTree) {
- if (this._cssAst.length > 0) return;
- for (let i = 0; i < this._document.styleSheets.length; i++) {
- let styleSheet = this._document.styleSheets[i];
- if (styleSheet.href) {
- let cssCode = await this.#getCssData(styleSheet.href);
- if (cssCode) {
- let ast = this.#cssCodeToAST(cssCode);
- this._cssAst.push({
- file: styleSheet.href,
- ast: ast,
- code:cssCode,
- });
- }
- }
- }
- }
- }
- async #getCssData(file) {
- let response = await fetch(file);
- return await response.text();
- }
- #cssCodeToAST(cssCode) {
- let curAst = this._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
- 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;
- }
- if (curSelector) selectors.push({
- name: curSelector.replaceAll("\r\n", " ") ,//curSelector,
- loc: node.prelude.loc,
- });
- }
- });
- return selectors;
- }
- //获取元素内联样式定义
- #getInlineStyle(ele) {
- let ret = [];
- for (let i = 0; i < ele.style.length; i++) {
- let sn = ele.style[i];
- let isImportant = false;
- if ((ele.style.cssText.split(sn)[1] + ";").split(";")[0].indexOf("!important") > 0) {
- isImportant = true;
- }
- ret.push({
- name: sn,
- cssText: ele.style[sn],
- isImportant: isImportant,
- });
- }
- if (ret.length <= 0) {
- return ret;
- }
- else {
- this._cssCount = _cssCount + 1;
- return [{
- selectorName: "",
- declarations: "",
- href: "",
- style: ret,
- id: `c_${this._cssCount}`,
- fileIndex: null,
- posIndex:null,
- }];
- }
- }
- //计算选择器的权重级别
- #getSelectorPriority(selector,opts) {
- //github.com/yibn2008/css-priority
- return CssPriority.parse(selector,opts);
- }
- //获取当前元素的匹配样式
- #getMatchStyle(ele) {
- if (!ele) return [];
- const matchedCSSRules = [];
- // 获取所有样式表
- for (let i = 0; i < this._document.styleSheets.length; i++) {
- const styleSheet = this._document.styleSheets[i];
- try {
- // 遍历每个样式表的规则
- for (let j = 0; j < styleSheet.cssRules.length; j++) {
- const rule = styleSheet.cssRules[j];
- let isMatch = false;
- if (rule.selectorText == ":root") {
- if (ele.tagName == "HTML") isMatch = true;
- }
- else if (rule.selectorText == ":link" || rule.selectorText == ":visited") {
- if (ele.tagName == "A") isMatch = true;
- }
- else if (rule.selectorText == ":disabled" || rule.selectorText == ":enabled") {
- if (["INPUT", "TEXTAREA","SELECT","BUTTON","FIELDSET"].indexOf(ele.tagName) >= 0) isMatch = true;
- }
- else {
- if (ele.matches(rule.selectorText)) {
- isMatch = true;
- }
- }
- if (isMatch) {
- let styles = [];
- if (rule.style) {
- let sn = "";
- for (let i = 0; i < rule.style.length; i++) {
- sn = rule.style[i];
- let isImportant = false;
- if ((rule.style.cssText.split(sn)[1] + ";").split(";")[0].indexOf("!important") > 0) {
- isImportant = true;
- }
- styles.push({
- name: sn,
- cssText: rule.style[sn],
- isImportant: isImportant,
- });
- }
- }
- this._cssCount = this._cssCount + 1;
- let loc = null;
- if (this._cssAst.length > 0) {
- let fileAst = this._cssAst.find((x) => x.file == styleSheet.href);
- if (fileAst) {
- let selectorAst = fileAst.ast.find((x) => x.name == rule.selectorText);
- if (selectorAst) loc = selectorAst.loc;
- }
- }
- matchedCSSRules.push({
- selectorName: rule.selectorText,
- declarations: rule.style,
- href: styleSheet.href || '',
- style: styles,
- id: `c_${this._cssCount}`,
- //priority: this.#getSelectorPriority(rule.selectorText, {line:1000000-(i * 1000000 + j)}),
- priority: this.#getSelectorPriority(rule.selectorText, { line: i * 1000000 + j}),
- fileIndex: i,
- posIndex: j,
- loc:loc,
- });
- }
- }
- } catch (error) {
- console.error('无法访问样式表', error);
- }
- }
- //根据优先级排序
- return matchedCSSRules.sort((a, b) => {
- return CssPriority.compare(a.priority, b.priority);
- });;
- }
- //获取父级样式
- #getParentStyle(ele) {
- let styleList = [];
- let level = 0;
- let pEle = ele.parentElement;
- while (pEle) {
- let inlineStyle = this.#getInlineStyle(pEle);
- if (inlineStyle.length > 0) {
- styleList.push({
- relationEle: pEle,
- relationTag: pEle.tagName,
- nodeLevel: level,
- kind: CssKind.inline,
- selector: inlineStyle,
- });
- level = level + 1;
- }
- let matchStyle = this.#getMatchStyle(pEle);
- if (matchStyle.length > 0) {
- styleList.push({
- relationEle: pEle,
- relationTag: pEle.tagName,
- nodeLevel: level,
- kind: CssKind.match,
- selector: matchStyle,
- });
- level = level + 1;
- }
- pEle = pEle.parentElement;
- /*
- if (pEle.tagName == "HTML") {
- pEle = null;
- }*/
- }
- return styleList;
- }
- #getTopSelector(ele, selector,selectorName,line) {
- let find = null;
- for (let gItem of selectorName.split(",")) {
- if (!ele.matches(gItem)) continue;
- if (!find) {
- find = {
- selector: selector,
- key: gItem,
- priority: this.#getSelectorPriority(gItem, {line:line}),
- }
- }
- else {
- let curPriority = this.#getSelectorPriority(gItem, { line: line });
- let ret = CssPriority.compare(find.priority, curPriority);
- if (ret > 0) {
- find.selector = selector;
- find.key = gItem;
- find.priority = curPriority;
- }
- }
- }
- return find;
- }
- async #getDefaultStyle(eleTag) {
- let tmpF = this._document.tmpF;
- let w = new Lock();
- let tmpFStyle = null;
- if (!tmpF) {
- tmpF = this._document.createElement("iframe");
- tmpF.style.display = "none";
- tmpF.setAttribute("srcDoc", `<${eleTag} id="tmpf"></${eleTag}>`);
- this._document.body.appendChild(tmpF);
- this._document.tmpF = tmpF;
- tmpF.onload = function () {
- tmpFStyle = getComputedStyle(tmpF.contentWindow.document.getElementById("tmpf"));
- w.resolve(tmpFStyle);
- }
- }
- else {
- tmpF.setAttribute("srcDoc", `<${eleTag} id="tmpf"></${eleTag}>`);
- tmpF.onload = function () {
- tmpFStyle = getComputedStyle(tmpF.contentWindow.document.getElementById("tmpf"));
- w.resolve(tmpFStyle);
- }
- }
- return await w.wait();
- }
- //获取非默认样式
- async #getComputedStyleProp(ele) {
- let ret = [];
- //获取默认样式
- let defaultStyle = await this.#getDefaultStyle(ele.tagName);
- //获取应用样式
- let computeStyle = getComputedStyle(ele);
- for (let i = 0; i < computeStyle.length - 1; i++) {
- let sn = computeStyle[i];
- if (computeStyle[sn] == defaultStyle[sn]) continue;
- ret.push({
- name: sn,
- cssText: computeStyle[sn],
- })
- }
-
- return ret;
- }
- //获取css的ast
- async getAst() {
- await this.#initCssAst();
- return this._cssAst();
- }
- async refers(ele, propName) {
- let ret = [];
- //初始化css的ast
- this.#initCssAst();
- let inlineStyle = this.#getInlineStyle(ele);//获取自身的样式配置
- let matchStyle = this.#getMatchStyle(ele);//获取匹配的样式配置
- let parentStyle = this.#getParentStyle(ele);//获取所有父级的样式配置
- let computeStyleProps = await this.#getComputedStyleProp(ele);//获取浏览器的非默认样式属性
-
- //插入内联样式
- if (inlineStyle.length > 0) {
- ret.push({
- kind: CssKind.inline,
- selector: inlineStyle.selector,
- parent: null,
- })
- }
- //插入匹配样式
- for (let matchItem of matchStyle) {
- ret.push({
- kind: CssKind.match,
- selector: matchItem,
- parent:null,
- })
- }
- let inheritedRelation = [];
- //分析继承样式
- for (let sty of computeStyleProps) {
- if (!this._inheritStyles.has(sty.name)) continue;//非继承属性,则不需要判断父级
- if (propName && sty.name != propName) continue;
- //遍历多级父级
- for (let psty of parentStyle) {
- if (psty.selector?.length <= 0) continue;
- //遍历父级中的选择器
- for (let selectorItem of psty.selector) {
- if (selectorItem.style.length <= 0) continue;
- let findProp = false;
- //遍历选择器的样式属性
- for (let pro of selectorItem.style) {
- if (pro.name == sty.name) {
- findProp = true;
- break;
- }
- }
- //找到了对应属性
- if (findProp) {
- let findSelector = inheritedRelation.find((x) => x.selector.id == selectorItem.id);
- if (!findSelector) {
- //选择器不存在,则插入
- inheritedRelation.push({
- kind: CssKind.parent,
- selector: selectorItem,
- inheritedInfo: psty,
- });
- }
- continue;
- }
- }
- }
- }
- //计算继承的优先顺序
- inheritedRelation = inheritedRelation.sort((a, b) => {
- if (a.inheritedInfo.nodeLevel < b.inheritedInfo.nodeLevel) {
- return -1;
- }
- else if (a.inheritedInfo.nodeLevel > b.inheritedInfo.nodeLevel) {
- return 1;
- }
- else {
- return 0
- }
- });
- ret = ret.concat(inheritedRelation);
- let propApplyRelation = [];
- //计算属性的匹配位置
- for (let prop of computeStyleProps) {
- if (propName && prop.name != propName) continue;
- let findSelector = null;
- let isImportant = false;
- //遍历关联的选择器
- for (let selector of ret) {
- //{kind:"",selector:{},parent:null }
- let isFindProp = false;
- //遍历样式属性
- for (let sty of selector.selector.style) {
- if (prop.name == sty.name) {
- isFindProp = true;
- //判断是否配置了important申明
- if (sty.isImportant) {
- isImportant = true;
- }
- break;
- }
- }
- //找到了对应的属性
- if (isFindProp) {
- if (!findSelector) {
- findSelector = selector;
- }
- else {
- if (selector.kind != CssKind.parent) {
- //important>inline>match -parent继承类中的important不会应用到子级元素
- if (isImportant) {
- //发现important定义则结束当前属性所在选择器的遍历
- findSelector = selector;
- break;
- }
- }
- else {
- //优先级inline>match>parent
- if (findSelector.kind < selector.kind) continue;
- }
- //处理kind相等的情况
- if (findSelector.selector.selectorName.indexOf(",") > 0 || selector.selector.selectorName.indexOf(",") > 0) {
- //判断逗号之间的选择器的优先级
- let findSelectorTop = this.#getTopSelector(ele, findSelector.selector, findSelector.selector.selectorName, findSelector.selector.priority[5]);
- let backupSelectorTop = this.#getTopSelector(ele, selector.selector, selector.selector.selectorName, selector.selector.priority[5]);
- let curCompare = CssPriority.compare(findSelectorTop.priority, backupSelectorTop.priority);
- if (curCompare > 0) {
- findSelector = selector;
- }
- }
- }
- }
- }
- if (findSelector) {
- propApplyRelation.push({
- name: prop.name,
- cssText: prop.cssText,
- selector: findSelector,
- }
- );
- }
- }
- return {
- applyRelation: propApplyRelation,
- applySelectors: ret,
- };
- }
- }
|