cssRefers.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. class CssPriority {
  2. static _TOKEN_ID = /(#[\w-]+)/g
  3. static _TOKEN_CLASS = /(\.[\w-]+)/g
  4. static _TOKEN_ATTR = /(\[[^[\]]+])/g
  5. static _TOKEN_PSEUDO_CLASS = /(:[\w-]+)/g
  6. static _TOKEN_PSEUDO_ELEM = /(::[\w-]+)/g
  7. static _TOKEN_ELEM = /([\w-]+)/g
  8. static _PSEUDO_ELEMS = [
  9. 'first-letter',
  10. 'last-letter',
  11. 'first-line',
  12. 'last-line',
  13. 'first-child',
  14. 'last-child',
  15. 'before',
  16. 'after'
  17. ]
  18. static _SYMBOL_B = '\x02'
  19. static _SYMBOL_C = '\x03'
  20. static _SYMBOL_D = '\x04'
  21. static compare(a, b) {
  22. //在sort应用中,返回<0,则a在b之前,>0则a在b之后
  23. for (let i = 0; i < a.length; i++) {
  24. if (a[i] !== b[i]) {
  25. //return a[i] - b[i]
  26. return b[i] - a[i];
  27. }
  28. }
  29. return 0
  30. }
  31. /**
  32. * parse then priority of selector (CSS Rule)
  33. * @param {String} selector css selector
  34. * @param {Object} opts
  35. * - {Boolean} opts.important is current rule important
  36. * - {Number} opts.line line number of css rule
  37. * @return {Array} array that contains 6 priority number
  38. */
  39. static parse(selector, opts) {
  40. opts = opts || {}
  41. // priority: [ important, style, R1, R2, R3, line ]
  42. let important = opts.important ? 1 : 0
  43. let line = opts.line || 0
  44. let priority = [important, 0, 0, 0, 0, line]
  45. selector = selector.replace(/(::?)([\w-]+)/g, (total, left, elem) => {
  46. if (CssPriority._PSEUDO_ELEMS.indexOf(elem) >= 0) {
  47. if (left === ':') {
  48. return '::' + elem
  49. } else {
  50. return total
  51. }
  52. } else {
  53. return total
  54. }
  55. })
  56. // replace with symbols
  57. selector = selector
  58. .replace(CssPriority._TOKEN_ATTR, CssPriority._SYMBOL_C)
  59. .replace(CssPriority._TOKEN_PSEUDO_ELEM, CssPriority._SYMBOL_D)
  60. .replace(CssPriority._TOKEN_PSEUDO_CLASS, CssPriority._SYMBOL_C)
  61. .replace(CssPriority._TOKEN_ID, CssPriority._SYMBOL_B)
  62. .replace(CssPriority._TOKEN_CLASS, CssPriority._SYMBOL_C)
  63. .replace(CssPriority._TOKEN_ELEM, CssPriority._SYMBOL_D)
  64. // count
  65. selector = selector.replace(/[\2\3\4]/g, (symbol) => {
  66. let idx = symbol.charCodeAt(0)
  67. priority[idx]++
  68. return '<' + idx + '>'
  69. })
  70. return priority
  71. }
  72. }
  73. class Lock {
  74. constructor() {
  75. this.promise = new Promise((resolve, reject) => {
  76. this.resolve = resolve;
  77. this.reject = reject;
  78. });
  79. }
  80. async wait() {
  81. try {
  82. let result = await this.promise;
  83. return result;
  84. } catch (error) {
  85. throw error;
  86. }
  87. }
  88. }
  89. class CssKind {
  90. static inline = 1;
  91. static match = 2;
  92. static parent = 3;
  93. }
  94. class CssRefers {
  95. _document;
  96. _cssTree;
  97. _cssAst = [];
  98. _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],
  99. ["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],
  100. ["caption-side", null], ["empty-cells", null], ["border-collapse", null],
  101. ["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],
  102. ["break-before", null], ["break-after", null], ["break-inside", null], ["line-height", null], ["cursor", null], ["quotes", null]
  103. ]);
  104. _cssCount = 0;
  105. constructor(doc, cssTree) {
  106. this._document = doc;
  107. this._cssTree = cssTree;
  108. }
  109. async #initCssAst() {
  110. if (this._cssTree) {
  111. if (this._cssAst.length > 0) return;
  112. for (let i = 0; i < this._document.styleSheets.length; i++) {
  113. let styleSheet = this._document.styleSheets[i];
  114. if (styleSheet.href) {
  115. let cssCode = await this.#getCssData(styleSheet.href);
  116. if (cssCode) {
  117. let ast = this.#cssCodeToAST(cssCode);
  118. this._cssAst.push({
  119. file: styleSheet.href,
  120. ast: ast,
  121. code:cssCode,
  122. });
  123. }
  124. }
  125. }
  126. }
  127. }
  128. async #getCssData(file) {
  129. let response = await fetch(file);
  130. return await response.text();
  131. }
  132. #cssCodeToAST(cssCode) {
  133. let curAst = this._cssTree.parse(cssCode, {
  134. parseAtrulePrelude: false,
  135. parseRulePrelude: false,
  136. parseValue: false,
  137. positions: true,
  138. });
  139. let selectors = [];
  140. //https://github.com/csstree/csstree/blob/master/docs/parsing.md#parsesource-options
  141. csstree.walk(curAst, (node) => {
  142. if (node.type === 'Rule')//node.type === 'Declaration'
  143. {
  144. let curSelector;
  145. if (node.prelude.children) {
  146. curSelector = node.prelude.children.map(child => child.raw);
  147. }
  148. else {
  149. curSelector = node.prelude.value;
  150. }
  151. if (curSelector) selectors.push({
  152. name: curSelector.replaceAll("\r\n", " ") ,//curSelector,
  153. loc: node.prelude.loc,
  154. });
  155. }
  156. });
  157. return selectors;
  158. }
  159. //获取元素内联样式定义
  160. #getInlineStyle(ele) {
  161. let ret = [];
  162. for (let i = 0; i < ele.style.length; i++) {
  163. let sn = ele.style[i];
  164. let isImportant = false;
  165. if ((ele.style.cssText.split(sn)[1] + ";").split(";")[0].indexOf("!important") > 0) {
  166. isImportant = true;
  167. }
  168. ret.push({
  169. name: sn,
  170. cssText: ele.style[sn],
  171. isImportant: isImportant,
  172. });
  173. }
  174. if (ret.length <= 0) {
  175. return ret;
  176. }
  177. else {
  178. this._cssCount = _cssCount + 1;
  179. return [{
  180. selectorName: "",
  181. declarations: "",
  182. href: "",
  183. style: ret,
  184. id: `c_${this._cssCount}`,
  185. fileIndex: null,
  186. posIndex:null,
  187. }];
  188. }
  189. }
  190. //计算选择器的权重级别
  191. #getSelectorPriority(selector,opts) {
  192. //github.com/yibn2008/css-priority
  193. return CssPriority.parse(selector,opts);
  194. }
  195. //获取当前元素的匹配样式
  196. #getMatchStyle(ele) {
  197. if (!ele) return [];
  198. const matchedCSSRules = [];
  199. // 获取所有样式表
  200. for (let i = 0; i < this._document.styleSheets.length; i++) {
  201. const styleSheet = this._document.styleSheets[i];
  202. try {
  203. // 遍历每个样式表的规则
  204. for (let j = 0; j < styleSheet.cssRules.length; j++) {
  205. const rule = styleSheet.cssRules[j];
  206. let isMatch = false;
  207. if (rule.selectorText == ":root") {
  208. if (ele.tagName == "HTML") isMatch = true;
  209. }
  210. else if (rule.selectorText == ":link" || rule.selectorText == ":visited") {
  211. if (ele.tagName == "A") isMatch = true;
  212. }
  213. else if (rule.selectorText == ":disabled" || rule.selectorText == ":enabled") {
  214. if (["INPUT", "TEXTAREA","SELECT","BUTTON","FIELDSET"].indexOf(ele.tagName) >= 0) isMatch = true;
  215. }
  216. else {
  217. if (ele.matches(rule.selectorText)) {
  218. isMatch = true;
  219. }
  220. }
  221. if (isMatch) {
  222. let styles = [];
  223. if (rule.style) {
  224. let sn = "";
  225. for (let i = 0; i < rule.style.length; i++) {
  226. sn = rule.style[i];
  227. let isImportant = false;
  228. if ((rule.style.cssText.split(sn)[1] + ";").split(";")[0].indexOf("!important") > 0) {
  229. isImportant = true;
  230. }
  231. styles.push({
  232. name: sn,
  233. cssText: rule.style[sn],
  234. isImportant: isImportant,
  235. });
  236. }
  237. }
  238. this._cssCount = this._cssCount + 1;
  239. let loc = null;
  240. if (this._cssAst.length > 0) {
  241. let fileAst = this._cssAst.find((x) => x.file == styleSheet.href);
  242. if (fileAst) {
  243. let selectorAst = fileAst.ast.find((x) => x.name == rule.selectorText);
  244. if (selectorAst) loc = selectorAst.loc;
  245. }
  246. }
  247. matchedCSSRules.push({
  248. selectorName: rule.selectorText,
  249. declarations: rule.style,
  250. href: styleSheet.href || '',
  251. style: styles,
  252. id: `c_${this._cssCount}`,
  253. //priority: this.#getSelectorPriority(rule.selectorText, {line:1000000-(i * 1000000 + j)}),
  254. priority: this.#getSelectorPriority(rule.selectorText, { line: i * 1000000 + j}),
  255. fileIndex: i,
  256. posIndex: j,
  257. loc:loc,
  258. });
  259. }
  260. }
  261. } catch (error) {
  262. console.error('无法访问样式表', error);
  263. }
  264. }
  265. //根据优先级排序
  266. return matchedCSSRules.sort((a, b) => {
  267. return CssPriority.compare(a.priority, b.priority);
  268. });;
  269. }
  270. //获取父级样式
  271. #getParentStyle(ele) {
  272. let styleList = [];
  273. let level = 0;
  274. let pEle = ele.parentElement;
  275. while (pEle) {
  276. let inlineStyle = this.#getInlineStyle(pEle);
  277. if (inlineStyle.length > 0) {
  278. styleList.push({
  279. relationEle: pEle,
  280. relationTag: pEle.tagName,
  281. nodeLevel: level,
  282. kind: CssKind.inline,
  283. selector: inlineStyle,
  284. });
  285. level = level + 1;
  286. }
  287. let matchStyle = this.#getMatchStyle(pEle);
  288. if (matchStyle.length > 0) {
  289. styleList.push({
  290. relationEle: pEle,
  291. relationTag: pEle.tagName,
  292. nodeLevel: level,
  293. kind: CssKind.match,
  294. selector: matchStyle,
  295. });
  296. level = level + 1;
  297. }
  298. pEle = pEle.parentElement;
  299. /*
  300. if (pEle.tagName == "HTML") {
  301. pEle = null;
  302. }*/
  303. }
  304. return styleList;
  305. }
  306. #getTopSelector(ele, selector,selectorName,line) {
  307. let find = null;
  308. for (let gItem of selectorName.split(",")) {
  309. if (!ele.matches(gItem)) continue;
  310. if (!find) {
  311. find = {
  312. selector: selector,
  313. key: gItem,
  314. priority: this.#getSelectorPriority(gItem, {line:line}),
  315. }
  316. }
  317. else {
  318. let curPriority = this.#getSelectorPriority(gItem, { line: line });
  319. let ret = CssPriority.compare(find.priority, curPriority);
  320. if (ret > 0) {
  321. find.selector = selector;
  322. find.key = gItem;
  323. find.priority = curPriority;
  324. }
  325. }
  326. }
  327. return find;
  328. }
  329. async #getDefaultStyle(eleTag) {
  330. let tmpF = this._document.tmpF;
  331. let w = new Lock();
  332. let tmpFStyle = null;
  333. if (!tmpF) {
  334. tmpF = this._document.createElement("iframe");
  335. tmpF.style.display = "none";
  336. tmpF.setAttribute("srcDoc", `<${eleTag} id="tmpf"></${eleTag}>`);
  337. this._document.body.appendChild(tmpF);
  338. this._document.tmpF = tmpF;
  339. tmpF.onload = function () {
  340. tmpFStyle = getComputedStyle(tmpF.contentWindow.document.getElementById("tmpf"));
  341. w.resolve(tmpFStyle);
  342. }
  343. }
  344. else {
  345. tmpF.setAttribute("srcDoc", `<${eleTag} id="tmpf"></${eleTag}>`);
  346. tmpF.onload = function () {
  347. tmpFStyle = getComputedStyle(tmpF.contentWindow.document.getElementById("tmpf"));
  348. w.resolve(tmpFStyle);
  349. }
  350. }
  351. return await w.wait();
  352. }
  353. //获取非默认样式
  354. async #getComputedStyleProp(ele) {
  355. let ret = [];
  356. //获取默认样式
  357. let defaultStyle = await this.#getDefaultStyle(ele.tagName);
  358. //获取应用样式
  359. let computeStyle = getComputedStyle(ele);
  360. for (let i = 0; i < computeStyle.length - 1; i++) {
  361. let sn = computeStyle[i];
  362. if (computeStyle[sn] == defaultStyle[sn]) continue;
  363. ret.push({
  364. name: sn,
  365. cssText: computeStyle[sn],
  366. })
  367. }
  368. return ret;
  369. }
  370. //获取css的ast
  371. async getAst() {
  372. await this.#initCssAst();
  373. return this._cssAst();
  374. }
  375. async refers(ele, propName) {
  376. let ret = [];
  377. //初始化css的ast
  378. this.#initCssAst();
  379. let inlineStyle = this.#getInlineStyle(ele);//获取自身的样式配置
  380. let matchStyle = this.#getMatchStyle(ele);//获取匹配的样式配置
  381. let parentStyle = this.#getParentStyle(ele);//获取所有父级的样式配置
  382. let computeStyleProps = await this.#getComputedStyleProp(ele);//获取浏览器的非默认样式属性
  383. //插入内联样式
  384. if (inlineStyle.length > 0) {
  385. ret.push({
  386. kind: CssKind.inline,
  387. selector: inlineStyle.selector,
  388. parent: null,
  389. })
  390. }
  391. //插入匹配样式
  392. for (let matchItem of matchStyle) {
  393. ret.push({
  394. kind: CssKind.match,
  395. selector: matchItem,
  396. parent:null,
  397. })
  398. }
  399. let inheritedRelation = [];
  400. //分析继承样式
  401. for (let sty of computeStyleProps) {
  402. if (!this._inheritStyles.has(sty.name)) continue;//非继承属性,则不需要判断父级
  403. if (propName && sty.name != propName) continue;
  404. //遍历多级父级
  405. for (let psty of parentStyle) {
  406. if (psty.selector?.length <= 0) continue;
  407. //遍历父级中的选择器
  408. for (let selectorItem of psty.selector) {
  409. if (selectorItem.style.length <= 0) continue;
  410. let findProp = false;
  411. //遍历选择器的样式属性
  412. for (let pro of selectorItem.style) {
  413. if (pro.name == sty.name) {
  414. findProp = true;
  415. break;
  416. }
  417. }
  418. //找到了对应属性
  419. if (findProp) {
  420. let findSelector = inheritedRelation.find((x) => x.selector.id == selectorItem.id);
  421. if (!findSelector) {
  422. //选择器不存在,则插入
  423. inheritedRelation.push({
  424. kind: CssKind.parent,
  425. selector: selectorItem,
  426. inheritedInfo: psty,
  427. });
  428. }
  429. continue;
  430. }
  431. }
  432. }
  433. }
  434. //计算继承的优先顺序
  435. inheritedRelation = inheritedRelation.sort((a, b) => {
  436. if (a.inheritedInfo.nodeLevel < b.inheritedInfo.nodeLevel) {
  437. return -1;
  438. }
  439. else if (a.inheritedInfo.nodeLevel > b.inheritedInfo.nodeLevel) {
  440. return 1;
  441. }
  442. else {
  443. return 0
  444. }
  445. });
  446. ret = ret.concat(inheritedRelation);
  447. let propApplyRelation = [];
  448. //计算属性的匹配位置
  449. for (let prop of computeStyleProps) {
  450. if (propName && prop.name != propName) continue;
  451. let findSelector = null;
  452. let isImportant = false;
  453. //遍历关联的选择器
  454. for (let selector of ret) {
  455. //{kind:"",selector:{},parent:null }
  456. let isFindProp = false;
  457. //遍历样式属性
  458. for (let sty of selector.selector.style) {
  459. if (prop.name == sty.name) {
  460. isFindProp = true;
  461. //判断是否配置了important申明
  462. if (sty.isImportant) {
  463. isImportant = true;
  464. }
  465. break;
  466. }
  467. }
  468. //找到了对应的属性
  469. if (isFindProp) {
  470. if (!findSelector) {
  471. findSelector = selector;
  472. }
  473. else {
  474. if (selector.kind != CssKind.parent) {
  475. //important>inline>match -parent继承类中的important不会应用到子级元素
  476. if (isImportant) {
  477. //发现important定义则结束当前属性所在选择器的遍历
  478. findSelector = selector;
  479. break;
  480. }
  481. }
  482. else {
  483. //优先级inline>match>parent
  484. if (findSelector.kind < selector.kind) continue;
  485. }
  486. //处理kind相等的情况
  487. if (findSelector.selector.selectorName.indexOf(",") > 0 || selector.selector.selectorName.indexOf(",") > 0) {
  488. //判断逗号之间的选择器的优先级
  489. let findSelectorTop = this.#getTopSelector(ele, findSelector.selector, findSelector.selector.selectorName, findSelector.selector.priority[5]);
  490. let backupSelectorTop = this.#getTopSelector(ele, selector.selector, selector.selector.selectorName, selector.selector.priority[5]);
  491. let curCompare = CssPriority.compare(findSelectorTop.priority, backupSelectorTop.priority);
  492. if (curCompare > 0) {
  493. findSelector = selector;
  494. }
  495. }
  496. }
  497. }
  498. }
  499. if (findSelector) {
  500. propApplyRelation.push({
  501. name: prop.name,
  502. cssText: prop.cssText,
  503. selector: findSelector,
  504. }
  505. );
  506. }
  507. }
  508. return {
  509. applyRelation: propApplyRelation,
  510. applySelectors: ret,
  511. };
  512. }
  513. }