套壳油猴的广告拦截脚本

将 ABP 中的元素隐藏规则转换为 CSS 使用

当前为 2023-05-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name AdBlock Script for WebView
  3. // @name:zh-CN 套壳油猴的广告拦截脚本
  4. // @author Lemon399
  5. // @version 2.5.2
  6. // @description Parse ABP Cosmetic rules to CSS and apply it.
  7. // @description:zh-CN 将 ABP 中的元素隐藏规则转换为 CSS 使用
  8. // @resource jiekouAD https://raw.iqiq.io/damengzhu/banad/main/jiekouAD.txt
  9. // @resource CSSRule https://raw.iqiq.io/damengzhu/abpmerge/main/CSSRule.txt
  10. // @match https://*/*
  11. // @match http://*/*
  12. // @run-at document-start
  13. // @grant unsafeWindow
  14. // @grant GM_registerMenuCommand
  15. // @grant GM_unregisterMenuCommand
  16. // @grant GM_getValue
  17. // @grant GM_deleteValue
  18. // @grant GM_setValue
  19. // @grant GM_addStyle
  20. // @grant GM_xmlhttpRequest
  21. // @grant GM_getResourceText
  22. // @namespace https://lemon399-bitbucket-io.vercel.app/
  23. // @source https://gitee.com/lemon399/tampermonkey-cli/tree/master/projects/abp_parse
  24. // @source https://bitbucket.org/lemon399/tampermonkey-cli/src/master/projects/abp_parse/
  25. // @connect raw.iqiq.io
  26. // @copyright GPL-3.0
  27. // @license GPL-3.0
  28. // ==/UserScript==
  29.  
  30. /* eslint-disable no-undef */
  31.  
  32. (function (tm) {
  33. "use strict";
  34.  
  35. function __awaiter(thisArg, _arguments, P, generator) {
  36. function adopt(value) {
  37. return value instanceof P
  38. ? value
  39. : new P(function (resolve) {
  40. resolve(value);
  41. });
  42. }
  43. return new (P || (P = Promise))(function (resolve, reject) {
  44. function fulfilled(value) {
  45. try {
  46. step(generator.next(value));
  47. } catch (e) {
  48. reject(e);
  49. }
  50. }
  51. function rejected(value) {
  52. try {
  53. step(generator["throw"](value));
  54. } catch (e) {
  55. reject(e);
  56. }
  57. }
  58. function step(result) {
  59. result.done
  60. ? resolve(result.value)
  61. : adopt(result.value).then(fulfilled, rejected);
  62. }
  63. step((generator = generator.apply(thisArg, _arguments || [])).next());
  64. });
  65. }
  66.  
  67. const presetCss =
  68. " {display: none !important;width: 0 !important;height: 0 !important;} ";
  69. const defaultRules = `
  70. ! 没有 ## #@# #?# #@?#
  71. ! #$# #@$# #$?# #@$?# 的行和
  72. ! 开头为 ! 的行会忽略
  73. !
  74. ! 由于语法限制,内置规则中
  75. ! 一个反斜杠需要改成两个,像这样 \\
  76. !
  77. ! 若要修改地址,请注意同步修改
  78. ! 头部的 @connect @resource
  79.  
  80. `;
  81. const onlineRules = [];
  82. onlineRules.push(
  83. {
  84. 标识: "jiekouAD",
  85. 地址: "https://raw.iqiq.io/damengzhu/banad/main/jiekouAD.txt",
  86. 在线更新: !!1,
  87. 筛选后存储: !!1,
  88. },
  89. {
  90. 标识: "CSSRule",
  91. 地址: "https://raw.iqiq.io/damengzhu/abpmerge/main/CSSRule.txt",
  92. 在线更新: !!1,
  93. 筛选后存储: !!0,
  94. }
  95. );
  96. const styleBoxes = ["genHideCss", "genExtraCss", "spcHideCss", "spcExtraCss"];
  97. const dataBoxes = ["selectors", "extSelectors", "styles", "extStyles"];
  98.  
  99. const CRRE =
  100. /^(~?[\w-]+(?:\.[\w-]+)*(?:\.[\w-]+|\.\*)(?:,~?[\w-]+(?:\.[\w-]+)*(?:\.[\w-]+|\.\*))*)?(#@?\$?\??#)([^\s^+].*)/,
  101. BRRE =
  102. /^(?:@@?)(?:\/(.*[^\\])\/|(\|\|?)?(https?:\/\/)?([^\s"<>`]+?[|^]?))\$((?:(?:~?[\w-]+(?:=[\s\w'":.-]+)?|_+)(?:[^\\],|$))+)/,
  103. CCRE = /^\/\* (\d)(.+?) \*\/ ((.+?) *{ *[a-zA-Z-]+ *: *.+}) *$/,
  104. BROpts = [
  105. "elemhide",
  106. "ehide",
  107. "specifichide",
  108. "shide",
  109. "generichide",
  110. "ghide",
  111. ];
  112. const CRFlags = ["##", "#@#", "#?#", "#@?#", "#$#", "#@$#", "#$?#", "#@$?#"];
  113. function bRuleSpliter(rule) {
  114. const group = rule.match(BRRE);
  115. if (!group) return null;
  116. const [, regex, pipe, proto, body, option] = group,
  117. options = option.split(","),
  118. sepChar = "[^\\w\\.%-]",
  119. anyChar = '(?:[^\\s"<>`]*)',
  120. eh = hasSome(options, ["elemhide", "ehide"]),
  121. sh = hasSome(options, ["specifichide", "shide"]),
  122. gh = hasSome(options, ["generichide", "ghide"]);
  123. let match = "";
  124. if (regex) {
  125. match = regex;
  126. } else {
  127. match += pipe
  128. ? proto
  129. ? `^${proto}`
  130. : `^https?://(?:[\\w-]+\\.)*?`
  131. : `^${anyChar}`;
  132. match += body
  133. .replace(/[-\\$+.()[\]{}]/g, "\\$&")
  134. .replace(/\^/g, "$^")
  135. .replace(/\|$/, "$")
  136. .replace(/\|/g, "\\|")
  137. .replace(/\*$/g, "")
  138. .replace(/\*/g, anyChar)
  139. .replace(/\$\^$/, `(?:${sepChar}.*|$)`)
  140. .replace(/\$\^/g, sepChar);
  141. }
  142. return {
  143. rule,
  144. match,
  145. level: eh ? 3 : gh && sh ? 3 : sh ? 2 : gh ? 1 : 0,
  146. };
  147. }
  148. function isBasicRule(rule) {
  149. return BRRE.test(rule) && hasSome(rule, BROpts);
  150. }
  151. function bRuleParser(rule, url = location.href) {
  152. return rule ? (new RegExp(rule.match).test(url) ? rule.level : 0) : 0;
  153. }
  154. function getEtag(header) {
  155. var _a;
  156. let result = null;
  157. if (!header) return null;
  158. [
  159. /(?:e|E)(?:t|T)ag: (?:W\/)?"(\w+)"/,
  160. // WebMonkey 系
  161. /(?:e|E)(?:t|T)ag: \[(?:W\/)?"(\w+)"\]/,
  162. // 书签地球
  163. /(?:e|E)(?:t|T)ag=(?:W\/)?"(\w+)"/,
  164. // 海阔世界
  165. /^(?:W\/)?"(\w+)"/,
  166. ].forEach((re) => {
  167. result !== null && result !== void 0
  168. ? result
  169. : (result = header.match(re));
  170. });
  171. return (_a = result === null || result === void 0 ? void 0 : result[1]) !==
  172. null && _a !== void 0
  173. ? _a
  174. : null;
  175. }
  176. function extrEtag(resp) {
  177. var _a, _b, _c;
  178. const etag = getEtag(
  179. typeof (resp === null || resp === void 0 ? void 0 : resp.headers) ==
  180. "object"
  181. ? // 海阔世界
  182. (_b =
  183. (_a = resp === null || resp === void 0 ? void 0 : resp.headers) ===
  184. null || _a === void 0
  185. ? void 0
  186. : _a.etag) === null || _b === void 0
  187. ? void 0
  188. : _b[0]
  189. : typeof (resp === null || resp === void 0
  190. ? void 0
  191. : resp.responseHeaders) == "string"
  192. ? // Tampermonkey
  193. resp === null || resp === void 0
  194. ? void 0
  195. : resp.responseHeaders
  196. : // Appara
  197. (_c =
  198. resp === null || resp === void 0
  199. ? void 0
  200. : resp.getAllResponseHeaders) === null || _c === void 0
  201. ? void 0
  202. : _c.call(resp)
  203. );
  204. return etag;
  205. }
  206. function makeRuleBox() {
  207. return {
  208. black: [],
  209. white: [],
  210. };
  211. }
  212. function domainChecker(domains) {
  213. const results = [],
  214. invResults = [],
  215. currDomain = location.hostname,
  216. urlSuffix = /\.+?[\w-]+$/.exec(currDomain);
  217. let totalResult = [0, false],
  218. black = false,
  219. white = false,
  220. match = false;
  221. domains.forEach((domain) => {
  222. if (domain.endsWith(".*") && Array.isArray(urlSuffix)) {
  223. domain = domain.replace(".*", urlSuffix[0]);
  224. }
  225. const invert = domain[0] === "~";
  226. if (invert) domain = domain.slice(1);
  227. const result = currDomain.endsWith(domain);
  228. if (invert) {
  229. if (result) white = true;
  230. invResults.push([domain.length, !result]);
  231. } else {
  232. if (result) black = true;
  233. results.push([domain.length, result]);
  234. }
  235. });
  236. if (results.length > 0 && !black) {
  237. match = false;
  238. } else if (invResults.length > 0 && !white) {
  239. match = true;
  240. } else {
  241. results.forEach((r) => {
  242. if (r[0] >= totalResult[0] && r[1]) {
  243. totalResult = r;
  244. }
  245. });
  246. invResults.forEach((r) => {
  247. if (r[0] >= totalResult[0] && !r[1]) {
  248. totalResult = r;
  249. }
  250. });
  251. match = totalResult[1];
  252. }
  253. return [match, results.length === 0];
  254. }
  255. function hasSome(str, arr) {
  256. return arr.some((word) => str.includes(word));
  257. }
  258. function ruleSpliter(rule) {
  259. const group = rule.match(CRRE);
  260. if (group) {
  261. const [, place = "*", flag, sel] = group,
  262. type = CRFlags.indexOf(flag),
  263. matchResult =
  264. place === "*" ? [true, true] : domainChecker(place.split(","));
  265. if (sel && matchResult[0]) {
  266. return {
  267. black: type % 2 ? "white" : "black",
  268. type: Math.floor(type / 2),
  269. place,
  270. generic: matchResult[1],
  271. sel,
  272. };
  273. }
  274. }
  275. }
  276. function ruleLoader(rule) {
  277. if (
  278. hasSome(rule, [
  279. ":matches-path(",
  280. ":min-text-length(",
  281. ":watch-attr(",
  282. ":-abp-properties(",
  283. ":matches-property(",
  284. ])
  285. )
  286. return;
  287. // 去掉开头空格
  288. rule = rule.replace(/^ +/, "");
  289. // 如果 #$# 不包含 {} 就排除
  290. // 可以尽量排除 Snippet Filters
  291. if (/(\w|^)#\$#/.test(rule) && !/{.+} *$/.test(rule)) return;
  292. // ## -> #?#
  293. if (
  294. /(\w|^)#@?\$?#/.test(rule) &&
  295. hasSome(rule, [
  296. ":has(",
  297. ":-abp-has(",
  298. "[-ext-has=",
  299. ":has-text(",
  300. ":contains(",
  301. ":-abp-contains(",
  302. "[-ext-contains=",
  303. ":matches-css(",
  304. "[-ext-matches-css=",
  305. ":matches-css-before(",
  306. "[-ext-matches-css-before=",
  307. ":matches-css-after(",
  308. "[-ext-matches-css-after=",
  309. ":matches-attr(",
  310. ":nth-ancestor(",
  311. ":upward(",
  312. ":xpath(",
  313. ":remove()",
  314. ":not(",
  315. ])
  316. ) {
  317. rule = rule.replace(/(\w|^)#(@?\$?)#/, "$1#$2?#");
  318. }
  319. // :style(...) 转换
  320. // example.com#?##id:style(color: red)
  321. // example.com#$?##id { color: red }
  322. if (rule.includes(":style(")) {
  323. rule = rule
  324. .replace(/(\w|^)#(@?)(\??)#/, "$1#$2$$$3#")
  325. .replace(/:style\(/, " { ")
  326. .replace(/\)$/, " }");
  327. }
  328. return ruleSpliter(rule);
  329. }
  330. function ruleToCss(rule) {
  331. var _a, _b;
  332. const isStyle = /} *$/.test(rule.sel);
  333. return [
  334. `/* ${rule.type}${rule.place} */ ${
  335. rule.sel + (!isStyle ? presetCss : "")
  336. } \n`,
  337. isStyle
  338. ? (_b =
  339. (_a = rule.sel.match(/^(.+?) *{ *[a-zA-Z-]+ *: *.+} *$/)) ===
  340. null || _a === void 0
  341. ? void 0
  342. : _a[1]) !== null && _b !== void 0
  343. ? _b
  344. : rule.sel
  345. : rule.sel,
  346. ];
  347. }
  348. function cssToAbp(css) {
  349. const flags = ["##", "#?#", "#$#", "#$?#"];
  350. const match = css.match(CCRE);
  351. if (match === null) return null;
  352. const type = parseInt(match[1]);
  353. return [
  354. `${match[2] === "*" ? "" : match[2]}${flags[type]}${
  355. type >= 2 ? match[3] : match[4]
  356. }`,
  357. type,
  358. match[4],
  359. ];
  360. }
  361. function downUrl(url, name) {
  362. const a = document.createElement("a");
  363. a.href = url;
  364. a.download = name;
  365. Object.assign(a.style, {
  366. position: "fixed",
  367. top: "200%",
  368. });
  369. document.body.appendChild(a);
  370. setTimeout(() => {
  371. a.click();
  372. a.remove();
  373. }, 0);
  374. }
  375.  
  376. const data = {
  377. disabled: false,
  378. saved: false,
  379. update: true,
  380. updating: false,
  381. receivedRules: "",
  382. customRules: defaultRules,
  383. allRules: "",
  384. genHideCss: "",
  385. genExtraCss: "",
  386. spcHideCss: "",
  387. spcExtraCss: "",
  388. selectors: makeRuleBox(),
  389. extSelectors: makeRuleBox(),
  390. styles: makeRuleBox(),
  391. extStyles: makeRuleBox(),
  392. bRules: [],
  393. appliedLevel: 0,
  394. appliedCount: 0,
  395. records: [],
  396. isFrame: tm.unsafeWindow.self !== tm.unsafeWindow.top,
  397. isClean: false,
  398. mutex: "__lemon__abp__parser__$__",
  399. timeout: 10000,
  400. xTimeout: 1000,
  401. tryCount: 5,
  402. tryTimeout: 500, // CSS 注入尝试间隔
  403. };
  404.  
  405. const emptyStyle = {
  406. needUpdate: true,
  407. genHideCss: "",
  408. genExtraCss: "",
  409. spcHideCss: "",
  410. spcExtraCss: "",
  411. };
  412. const values = {
  413. get black() {
  414. const arrStr = gmValue("get", false, "ajs_disabled_domains", "");
  415. return typeof arrStr == "string" && arrStr.length > 0
  416. ? arrStr.split(",")
  417. : [];
  418. },
  419. set black(v) {
  420. gmValue(
  421. "set",
  422. false,
  423. "ajs_disabled_domains",
  424. v === null || v === void 0 ? void 0 : v.join()
  425. );
  426. },
  427. get rules() {
  428. return gmValue("get", true, "ajs_saved_abprules", {});
  429. },
  430. set rules(v) {
  431. gmValue("set", true, "ajs_saved_abprules", v);
  432. },
  433. get css() {
  434. return gmValue(
  435. "get",
  436. true,
  437. `ajs_saved_styles_${location.hostname}`,
  438. emptyStyle
  439. );
  440. },
  441. set css(v) {
  442. gmValue("set", true, `ajs_saved_styles_${location.hostname}`, v);
  443. },
  444. get hasSave() {
  445. const arrStr = gmValue("get", false, "ajs_hasSave_domains", "");
  446. return typeof arrStr == "string" && arrStr.length > 0
  447. ? arrStr.split(",")
  448. : [];
  449. },
  450. set hasSave(v) {
  451. gmValue(
  452. "set",
  453. false,
  454. "ajs_hasSave_domains",
  455. v === null || v === void 0 ? void 0 : v.join()
  456. );
  457. },
  458. get time() {
  459. return gmValue("get", false, "ajs_rules_ver", "0/0/0 0:0:0");
  460. },
  461. set time(v) {
  462. gmValue("set", false, "ajs_rules_ver", v);
  463. },
  464. get etags() {
  465. return gmValue("get", true, "ajs_rules_etags", {});
  466. },
  467. set etags(v) {
  468. gmValue("set", true, "ajs_rules_etags", v);
  469. },
  470. get brules() {
  471. return gmValue("get", true, "ajs_modifier_rules", []);
  472. },
  473. set brules(v) {
  474. gmValue("set", true, "ajs_modifier_rules", v);
  475. },
  476. },
  477. menus = {
  478. disable: {
  479. id: undefined,
  480. get text() {
  481. return data.disabled ? "在此网站启用拦截" : "在此网站禁用拦截";
  482. },
  483. },
  484. update: {
  485. id: undefined,
  486. get text() {
  487. const time = values.time;
  488. return data.updating
  489. ? "正在更新..."
  490. : `点击更新: ${time.slice(0, 1) === "0" ? "未知时间" : time}`;
  491. },
  492. },
  493. count: {
  494. id: undefined,
  495. get text() {
  496. var _a;
  497. let cssCount = "";
  498. if ((data.appliedLevel & 1) == 0)
  499. cssCount += data.genHideCss + data.genExtraCss;
  500. if ((data.appliedLevel & 2) == 0)
  501. cssCount += data.spcHideCss + data.spcExtraCss;
  502. return data.isClean
  503. ? "已清空,点击刷新重新加载规则"
  504. : `${
  505. data.saved
  506. ? "CSS: " +
  507. ((_a = cssCount.match(/{/g)) === null || _a === void 0
  508. ? void 0
  509. : _a.length)
  510. : "规则: " +
  511. data.appliedCount +
  512. "/" +
  513. data.allRules.split("\n").length
  514. },点击清空规则`;
  515. },
  516. },
  517. export: {
  518. id: undefined,
  519. text: "下载统计报告",
  520. },
  521. };
  522. function gmMenu(name, cb) {
  523. var _a;
  524. const id = (_a = menus[name].id) !== null && _a !== void 0 ? _a : undefined;
  525. if (
  526. typeof tm.GM_registerMenuCommand != "function" ||
  527. typeof tm.GM_unregisterMenuCommand != "function" ||
  528. data.isFrame
  529. )
  530. return;
  531. if (typeof id != "undefined") {
  532. tm.GM_unregisterMenuCommand(id);
  533. menus[name].id = undefined;
  534. }
  535. if (typeof cb == "function") {
  536. menus[name].id = tm.GM_registerMenuCommand(menus[name].text, cb);
  537. }
  538. }
  539. function gmValue(action, json, key, value) {
  540. switch (action) {
  541. case "get":
  542. try {
  543. const v = tm.GM_getValue(key, json ? JSON.stringify(value) : value);
  544. return json && typeof v == "string" ? JSON.parse(v) : v;
  545. } catch (error) {
  546. return;
  547. }
  548. case "set":
  549. try {
  550. value === null || value === undefined
  551. ? tm.GM_deleteValue(key)
  552. : tm.GM_setValue(key, json ? JSON.stringify(value) : value);
  553. } catch (error) {
  554. tm.GM_deleteValue(key);
  555. }
  556. break;
  557. }
  558. }
  559. function addStyle(css, pass = 0) {
  560. let el;
  561. if (pass >= data.tryCount) return;
  562. if (typeof tm.GM_addStyle == "function") {
  563. el = tm.GM_addStyle(css);
  564. } else {
  565. el = document.createElement("style");
  566. el.textContent = css;
  567. document.documentElement.appendChild(el);
  568. }
  569. if (!el || !document.documentElement.contains(el)) {
  570. setTimeout(() => {
  571. addStyle(css, pass + 1);
  572. }, data.tryTimeout);
  573. }
  574. }
  575. function promiseXhr(details) {
  576. return __awaiter(this, void 0, void 0, function* () {
  577. let loaded = false;
  578. try {
  579. return yield new Promise((resolve, reject) => {
  580. tm.GM_xmlhttpRequest(
  581. Object.assign(
  582. {
  583. onload(e) {
  584. loaded = true;
  585. resolve(e);
  586. },
  587. onabort: reject.bind(null, "abort"),
  588. onerror(e) {
  589. reject({
  590. error: "error",
  591. resp: e,
  592. });
  593. },
  594. ontimeout: reject.bind(null, "timeout"),
  595. onreadystatechange(e) {
  596. // X 浏览器超时中断
  597. if (
  598. (e === null || e === void 0 ? void 0 : e.readyState) === 4
  599. ) {
  600. setTimeout(() => {
  601. if (!loaded)
  602. reject({
  603. error: "X timeout",
  604. resp: e,
  605. });
  606. }, data.xTimeout);
  607. }
  608. // Via 浏览器超时中断,不给成功状态...
  609. if (
  610. (e === null || e === void 0 ? void 0 : e.readyState) === 3
  611. ) {
  612. setTimeout(() => {
  613. if (!loaded)
  614. reject({
  615. error: "Via timeout",
  616. resp: e,
  617. });
  618. }, data.timeout);
  619. }
  620. },
  621. timeout: data.timeout,
  622. },
  623. details
  624. )
  625. );
  626. });
  627. } catch (error) {
  628. console.error("规则: ", details.url, " 意外错误: ", error);
  629. }
  630. });
  631. }
  632. function getComments() {
  633. var _a, _b, _c;
  634. return (_c =
  635. (_b =
  636. (_a =
  637. tm.GM_info === null || tm.GM_info === void 0
  638. ? void 0
  639. : tm.GM_info.script) === null || _a === void 0
  640. ? void 0
  641. : _a.options) === null || _b === void 0
  642. ? void 0
  643. : _b.comment) !== null && _c !== void 0
  644. ? _c
  645. : "";
  646. }
  647. function getRuleFromResource(key) {
  648. try {
  649. return tm.GM_getResourceText(key);
  650. } catch (error) {
  651. return null;
  652. }
  653. }
  654.  
  655. function saveCss() {
  656. const styles = {
  657. needUpdate: false,
  658. genHideCss: data.genHideCss,
  659. genExtraCss: data.genExtraCss,
  660. spcHideCss: data.spcHideCss,
  661. spcExtraCss: data.spcExtraCss,
  662. },
  663. has = values.hasSave;
  664. values.css = styles;
  665. if (!has.includes(location.hostname)) has.push(location.hostname);
  666. values.hasSave = has;
  667. }
  668. function readCss() {
  669. const styles = values.css;
  670. if (!hasSome(Object.keys(styles), styleBoxes)) {
  671. values.css = emptyStyle;
  672. return false;
  673. }
  674. styleBoxes.forEach((sname) => {
  675. var _a;
  676. if (styles[sname].length > 0) {
  677. data.saved = true;
  678. data.update =
  679. (_a = styles.needUpdate) !== null && _a !== void 0 ? _a : true;
  680. data[sname] = styles[sname];
  681. }
  682. });
  683. return data.saved;
  684. }
  685.  
  686. function _defineProperty(obj, key, value) {
  687. if (key in obj) {
  688. Object.defineProperty(obj, key, {
  689. value: value,
  690. enumerable: true,
  691. configurable: true,
  692. writable: true,
  693. });
  694. } else {
  695. obj[key] = value;
  696. }
  697. return obj;
  698. }
  699. const NODE = {
  700. SELECTOR_LIST: "SelectorList",
  701. SELECTOR: "Selector",
  702. REGULAR_SELECTOR: "RegularSelector",
  703. EXTENDED_SELECTOR: "ExtendedSelector",
  704. ABSOLUTE_PSEUDO_CLASS: "AbsolutePseudoClass",
  705. RELATIVE_PSEUDO_CLASS: "RelativePseudoClass",
  706. };
  707. class AnySelectorNode {
  708. constructor(type) {
  709. _defineProperty(this, "children", []);
  710. this.type = type;
  711. }
  712. addChild(child) {
  713. this.children.push(child);
  714. }
  715. }
  716. class RegularSelectorNode extends AnySelectorNode {
  717. constructor(value) {
  718. super(NODE.REGULAR_SELECTOR);
  719. this.value = value;
  720. }
  721. }
  722. class RelativePseudoClassNode extends AnySelectorNode {
  723. constructor(name) {
  724. super(NODE.RELATIVE_PSEUDO_CLASS);
  725. this.name = name;
  726. }
  727. }
  728. class AbsolutePseudoClassNode extends AnySelectorNode {
  729. constructor(name) {
  730. super(NODE.ABSOLUTE_PSEUDO_CLASS);
  731. _defineProperty(this, "value", "");
  732. this.name = name;
  733. }
  734. }
  735. const LEFT_SQUARE_BRACKET = "[";
  736. const RIGHT_SQUARE_BRACKET = "]";
  737. const LEFT_PARENTHESIS = "(";
  738. const RIGHT_PARENTHESIS = ")";
  739. const LEFT_CURLY_BRACKET = "{";
  740. const RIGHT_CURLY_BRACKET = "}";
  741. const BRACKET = {
  742. SQUARE: {
  743. LEFT: LEFT_SQUARE_BRACKET,
  744. RIGHT: RIGHT_SQUARE_BRACKET,
  745. },
  746. PARENTHESES: {
  747. LEFT: LEFT_PARENTHESIS,
  748. RIGHT: RIGHT_PARENTHESIS,
  749. },
  750. CURLY: {
  751. LEFT: LEFT_CURLY_BRACKET,
  752. RIGHT: RIGHT_CURLY_BRACKET,
  753. },
  754. };
  755. const SLASH = "/";
  756. const BACKSLASH = "\\";
  757. const SPACE = " ";
  758. const COMMA = ",";
  759. const DOT = ".";
  760. const SEMICOLON = ";";
  761. const COLON = ":";
  762. const SINGLE_QUOTE = "'";
  763. const DOUBLE_QUOTE = '"';
  764. const CARET = "^";
  765. const DOLLAR_SIGN = "$";
  766. const EQUAL_SIGN = "=";
  767. const TAB = "\t";
  768. const CARRIAGE_RETURN = "\r";
  769. const LINE_FEED = "\n";
  770. const FORM_FEED = "\f";
  771. const WHITE_SPACE_CHARACTERS = [
  772. SPACE,
  773. TAB,
  774. CARRIAGE_RETURN,
  775. LINE_FEED,
  776. FORM_FEED,
  777. ];
  778. const ASTERISK = "*";
  779. const ID_MARKER = "#";
  780. const CLASS_MARKER = DOT;
  781. const DESCENDANT_COMBINATOR = SPACE;
  782. const CHILD_COMBINATOR = ">";
  783. const NEXT_SIBLING_COMBINATOR = "+";
  784. const SUBSEQUENT_SIBLING_COMBINATOR = "~";
  785. const COMBINATORS = [
  786. DESCENDANT_COMBINATOR,
  787. CHILD_COMBINATOR,
  788. NEXT_SIBLING_COMBINATOR,
  789. SUBSEQUENT_SIBLING_COMBINATOR,
  790. ];
  791. const SUPPORTED_SELECTOR_MARKS = [
  792. LEFT_SQUARE_BRACKET,
  793. RIGHT_SQUARE_BRACKET,
  794. LEFT_PARENTHESIS,
  795. RIGHT_PARENTHESIS,
  796. LEFT_CURLY_BRACKET,
  797. RIGHT_CURLY_BRACKET,
  798. SLASH,
  799. BACKSLASH,
  800. SEMICOLON,
  801. COLON,
  802. COMMA,
  803. SINGLE_QUOTE,
  804. DOUBLE_QUOTE,
  805. CARET,
  806. DOLLAR_SIGN,
  807. ASTERISK,
  808. ID_MARKER,
  809. CLASS_MARKER,
  810. DESCENDANT_COMBINATOR,
  811. CHILD_COMBINATOR,
  812. NEXT_SIBLING_COMBINATOR,
  813. SUBSEQUENT_SIBLING_COMBINATOR,
  814. TAB,
  815. CARRIAGE_RETURN,
  816. LINE_FEED,
  817. FORM_FEED,
  818. ];
  819. const SUPPORTED_STYLE_DECLARATION_MARKS = [
  820. COLON,
  821. SEMICOLON,
  822. SINGLE_QUOTE,
  823. DOUBLE_QUOTE,
  824. BACKSLASH,
  825. SPACE,
  826. TAB,
  827. CARRIAGE_RETURN,
  828. LINE_FEED,
  829. FORM_FEED,
  830. ];
  831. const CONTAINS_PSEUDO = "contains";
  832. const HAS_TEXT_PSEUDO = "has-text";
  833. const ABP_CONTAINS_PSEUDO = "-abp-contains";
  834. const MATCHES_CSS_PSEUDO = "matches-css";
  835. const MATCHES_CSS_BEFORE_PSEUDO = "matches-css-before";
  836. const MATCHES_CSS_AFTER_PSEUDO = "matches-css-after";
  837. const MATCHES_ATTR_PSEUDO_CLASS_MARKER = "matches-attr";
  838. const MATCHES_PROPERTY_PSEUDO_CLASS_MARKER = "matches-property";
  839. const XPATH_PSEUDO_CLASS_MARKER = "xpath";
  840. const NTH_ANCESTOR_PSEUDO_CLASS_MARKER = "nth-ancestor";
  841. const CONTAINS_PSEUDO_NAMES = [
  842. CONTAINS_PSEUDO,
  843. HAS_TEXT_PSEUDO,
  844. ABP_CONTAINS_PSEUDO,
  845. ];
  846. const UPWARD_PSEUDO_CLASS_MARKER = "upward";
  847. const REMOVE_PSEUDO_MARKER = "remove";
  848. const HAS_PSEUDO_CLASS_MARKER = "has";
  849. const ABP_HAS_PSEUDO_CLASS_MARKER = "-abp-has";
  850. const HAS_PSEUDO_CLASS_MARKERS = [
  851. HAS_PSEUDO_CLASS_MARKER,
  852. ABP_HAS_PSEUDO_CLASS_MARKER,
  853. ];
  854. const IS_PSEUDO_CLASS_MARKER = "is";
  855. const NOT_PSEUDO_CLASS_MARKER = "not";
  856. const ABSOLUTE_PSEUDO_CLASSES = [
  857. CONTAINS_PSEUDO,
  858. HAS_TEXT_PSEUDO,
  859. ABP_CONTAINS_PSEUDO,
  860. MATCHES_CSS_PSEUDO,
  861. MATCHES_CSS_BEFORE_PSEUDO,
  862. MATCHES_CSS_AFTER_PSEUDO,
  863. MATCHES_ATTR_PSEUDO_CLASS_MARKER,
  864. MATCHES_PROPERTY_PSEUDO_CLASS_MARKER,
  865. XPATH_PSEUDO_CLASS_MARKER,
  866. NTH_ANCESTOR_PSEUDO_CLASS_MARKER,
  867. UPWARD_PSEUDO_CLASS_MARKER,
  868. ];
  869. const RELATIVE_PSEUDO_CLASSES = [
  870. ...HAS_PSEUDO_CLASS_MARKERS,
  871. IS_PSEUDO_CLASS_MARKER,
  872. NOT_PSEUDO_CLASS_MARKER,
  873. ];
  874. const SUPPORTED_PSEUDO_CLASSES = [
  875. ...ABSOLUTE_PSEUDO_CLASSES,
  876. ...RELATIVE_PSEUDO_CLASSES,
  877. ];
  878. const OPTIMIZATION_PSEUDO_CLASSES = [
  879. NOT_PSEUDO_CLASS_MARKER,
  880. IS_PSEUDO_CLASS_MARKER,
  881. ];
  882. const SCOPE_CSS_PSEUDO_CLASS = ":scope";
  883. const REGULAR_PSEUDO_ELEMENTS = {
  884. AFTER: "after",
  885. BACKDROP: "backdrop",
  886. BEFORE: "before",
  887. CUE: "cue",
  888. CUE_REGION: "cue-region",
  889. FIRST_LETTER: "first-letter",
  890. FIRST_LINE: "first-line",
  891. FILE_SELECTION_BUTTON: "file-selector-button",
  892. GRAMMAR_ERROR: "grammar-error",
  893. MARKER: "marker",
  894. PART: "part",
  895. PLACEHOLDER: "placeholder",
  896. SELECTION: "selection",
  897. SLOTTED: "slotted",
  898. SPELLING_ERROR: "spelling-error",
  899. TARGET_TEXT: "target-text",
  900. };
  901. const AT_RULE_MARKER = "@";
  902. const CONTENT_CSS_PROPERTY = "content";
  903. const PSEUDO_PROPERTY_POSITIVE_VALUE = "true";
  904. const DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE = "global";
  905. const NO_SELECTOR_ERROR_PREFIX = "Selector should be defined";
  906. const STYLE_ERROR_PREFIX = {
  907. NO_STYLE: "No style declaration found",
  908. NO_SELECTOR: `${NO_SELECTOR_ERROR_PREFIX} before style declaration in stylesheet`,
  909. INVALID_STYLE: "Invalid style declaration",
  910. UNCLOSED_STYLE: "Unclosed style declaration",
  911. NO_PROPERTY: "Missing style property in declaration",
  912. NO_VALUE: "Missing style value in declaration",
  913. NO_STYLE_OR_REMOVE:
  914. "Style should be declared or :remove() pseudo-class should used",
  915. NO_COMMENT: "Comments are not supported",
  916. };
  917. const NO_AT_RULE_ERROR_PREFIX = "At-rules are not supported";
  918. const REMOVE_ERROR_PREFIX = {
  919. INVALID_REMOVE: "Invalid :remove() pseudo-class in selector",
  920. NO_TARGET_SELECTOR: `${NO_SELECTOR_ERROR_PREFIX} before :remove() pseudo-class`,
  921. MULTIPLE_USAGE: "Pseudo-class :remove() appears more than once in selector",
  922. INVALID_POSITION: "Pseudo-class :remove() should be at the end of selector",
  923. };
  924. const MATCHING_ELEMENT_ERROR_PREFIX = "Error while matching element";
  925. const MAX_STYLE_PROTECTION_COUNT = 50;
  926. const REGEXP_VALID_OLD_SYNTAX =
  927. /\[-(?:ext)-([a-z-_]+)=(["'])((?:(?=(\\?))\4.)*?)\2\]/g;
  928. const INVALID_OLD_SYNTAX_MARKER = "[-ext-";
  929. const evaluateMatch = (match, name, quoteChar, rawValue) => {
  930. const re = new RegExp(`([^\\\\]|^)\\\\${quoteChar}`, "g");
  931. const value = rawValue.replace(re, `$1${quoteChar}`);
  932. return `:${name}(${value})`;
  933. };
  934. const SCOPE_MARKER_REGEXP = /\(:scope >/g;
  935. const SCOPE_REPLACER = "(>";
  936. const MATCHES_CSS_PSEUDO_ELEMENT_REGEXP = /(:matches-css)-(before|after)\(/g;
  937. const convertMatchesCss = (
  938. match,
  939. extendedPseudoClass,
  940. regularPseudoElement
  941. ) => {
  942. return `${extendedPseudoClass}${BRACKET.PARENTHESES.LEFT}${regularPseudoElement}${COMMA}`;
  943. };
  944. const normalize = (selector) => {
  945. const normalizedSelector = selector
  946. .replace(REGEXP_VALID_OLD_SYNTAX, evaluateMatch)
  947. .replace(SCOPE_MARKER_REGEXP, SCOPE_REPLACER)
  948. .replace(MATCHES_CSS_PSEUDO_ELEMENT_REGEXP, convertMatchesCss);
  949. if (normalizedSelector.includes(INVALID_OLD_SYNTAX_MARKER)) {
  950. throw new Error(
  951. `Invalid extended-css old syntax selector: '${selector}'`
  952. );
  953. }
  954. return normalizedSelector;
  955. };
  956. const convert = (rawSelector) => {
  957. const trimmedSelector = rawSelector.trim();
  958. return normalize(trimmedSelector);
  959. };
  960. const TOKEN_TYPE = {
  961. MARK: "mark",
  962. WORD: "word",
  963. };
  964. const tokenize = (input, supportedMarks) => {
  965. let wordBuffer = "";
  966. const tokens = [];
  967. const selectorSymbols = input.split("");
  968. selectorSymbols.forEach((symbol) => {
  969. if (supportedMarks.includes(symbol)) {
  970. if (wordBuffer.length > 0) {
  971. tokens.push({
  972. type: TOKEN_TYPE.WORD,
  973. value: wordBuffer,
  974. });
  975. wordBuffer = "";
  976. }
  977. tokens.push({
  978. type: TOKEN_TYPE.MARK,
  979. value: symbol,
  980. });
  981. return;
  982. }
  983. wordBuffer += symbol;
  984. });
  985. if (wordBuffer.length > 0) {
  986. tokens.push({
  987. type: TOKEN_TYPE.WORD,
  988. value: wordBuffer,
  989. });
  990. }
  991. return tokens;
  992. };
  993. const tokenizeSelector = (rawSelector) => {
  994. const selector = convert(rawSelector);
  995. return tokenize(selector, SUPPORTED_SELECTOR_MARKS);
  996. };
  997. const tokenizeAttribute = (attribute) => {
  998. return tokenize(attribute, [...SUPPORTED_SELECTOR_MARKS, EQUAL_SIGN]);
  999. };
  1000. const flatten = (input) => {
  1001. const stack = [];
  1002. input.forEach((el) => stack.push(el));
  1003. const res = [];
  1004. while (stack.length) {
  1005. const next = stack.pop();
  1006. if (!next) {
  1007. throw new Error("Unable to make array flat");
  1008. }
  1009. if (Array.isArray(next)) {
  1010. next.forEach((el) => stack.push(el));
  1011. } else {
  1012. res.push(next);
  1013. }
  1014. }
  1015. return res.reverse();
  1016. };
  1017. const getFirst = (array) => {
  1018. return array[0];
  1019. };
  1020. const getLast = (array) => {
  1021. return array[array.length - 1];
  1022. };
  1023. const getPrevToLast = (array) => {
  1024. return array[array.length - 2];
  1025. };
  1026. const getItemByIndex = (array, index, errorMessage) => {
  1027. const indexChild = array[index];
  1028. if (!indexChild) {
  1029. throw new Error(errorMessage || `No array item found by index ${index}`);
  1030. }
  1031. return indexChild;
  1032. };
  1033. const NO_REGULAR_SELECTOR_ERROR =
  1034. "At least one of Selector node children should be RegularSelector";
  1035. const isSelectorListNode = (astNode) => {
  1036. return (
  1037. (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
  1038. NODE.SELECTOR_LIST
  1039. );
  1040. };
  1041. const isSelectorNode = (astNode) => {
  1042. return (
  1043. (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
  1044. NODE.SELECTOR
  1045. );
  1046. };
  1047. const isRegularSelectorNode = (astNode) => {
  1048. return (
  1049. (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
  1050. NODE.REGULAR_SELECTOR
  1051. );
  1052. };
  1053. const isExtendedSelectorNode = (astNode) => {
  1054. return astNode.type === NODE.EXTENDED_SELECTOR;
  1055. };
  1056. const isAbsolutePseudoClassNode = (astNode) => {
  1057. return (
  1058. (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
  1059. NODE.ABSOLUTE_PSEUDO_CLASS
  1060. );
  1061. };
  1062. const isRelativePseudoClassNode = (astNode) => {
  1063. return (
  1064. (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
  1065. NODE.RELATIVE_PSEUDO_CLASS
  1066. );
  1067. };
  1068. const getNodeName = (astNode) => {
  1069. if (astNode === null) {
  1070. throw new Error("Ast node should be defined");
  1071. }
  1072. if (
  1073. !isAbsolutePseudoClassNode(astNode) &&
  1074. !isRelativePseudoClassNode(astNode)
  1075. ) {
  1076. throw new Error(
  1077. "Only AbsolutePseudoClass or RelativePseudoClass ast node can have a name"
  1078. );
  1079. }
  1080. if (!astNode.name) {
  1081. throw new Error("Extended pseudo-class should have a name");
  1082. }
  1083. return astNode.name;
  1084. };
  1085. const getNodeValue = (astNode, errorMessage) => {
  1086. if (astNode === null) {
  1087. throw new Error("Ast node should be defined");
  1088. }
  1089. if (
  1090. !isRegularSelectorNode(astNode) &&
  1091. !isAbsolutePseudoClassNode(astNode)
  1092. ) {
  1093. throw new Error(
  1094. "Only RegularSelector ot AbsolutePseudoClass ast node can have a value"
  1095. );
  1096. }
  1097. if (!astNode.value) {
  1098. throw new Error(
  1099. errorMessage ||
  1100. "Ast RegularSelector ot AbsolutePseudoClass node should have a value"
  1101. );
  1102. }
  1103. return astNode.value;
  1104. };
  1105. const getRegularSelectorNodes = (children) => {
  1106. return children.filter(isRegularSelectorNode);
  1107. };
  1108. const getFirstRegularChild = (children, errorMessage) => {
  1109. const regularSelectorNodes = getRegularSelectorNodes(children);
  1110. const firstRegularSelectorNode = getFirst(regularSelectorNodes);
  1111. if (!firstRegularSelectorNode) {
  1112. throw new Error(errorMessage || NO_REGULAR_SELECTOR_ERROR);
  1113. }
  1114. return firstRegularSelectorNode;
  1115. };
  1116. const getLastRegularChild = (children) => {
  1117. const regularSelectorNodes = getRegularSelectorNodes(children);
  1118. const lastRegularSelectorNode = getLast(regularSelectorNodes);
  1119. if (!lastRegularSelectorNode) {
  1120. throw new Error(NO_REGULAR_SELECTOR_ERROR);
  1121. }
  1122. return lastRegularSelectorNode;
  1123. };
  1124. const getNodeOnlyChild = (node, errorMessage) => {
  1125. if (node.children.length !== 1) {
  1126. throw new Error(errorMessage);
  1127. }
  1128. const onlyChild = getFirst(node.children);
  1129. if (!onlyChild) {
  1130. throw new Error(errorMessage);
  1131. }
  1132. return onlyChild;
  1133. };
  1134. const getPseudoClassNode = (extendedSelectorNode) => {
  1135. return getNodeOnlyChild(
  1136. extendedSelectorNode,
  1137. "Extended selector should be specified"
  1138. );
  1139. };
  1140. const getRelativeSelectorListNode = (pseudoClassNode) => {
  1141. if (!isRelativePseudoClassNode(pseudoClassNode)) {
  1142. throw new Error(
  1143. "Only RelativePseudoClass node can have relative SelectorList node as child"
  1144. );
  1145. }
  1146. return getNodeOnlyChild(
  1147. pseudoClassNode,
  1148. `Missing arg for :${getNodeName(pseudoClassNode)}() pseudo-class`
  1149. );
  1150. };
  1151. const ATTRIBUTE_CASE_INSENSITIVE_FLAG = "i";
  1152. const POSSIBLE_MARKS_BEFORE_REGEXP = {
  1153. COMMON: [
  1154. BRACKET.PARENTHESES.LEFT,
  1155. SINGLE_QUOTE,
  1156. DOUBLE_QUOTE,
  1157. EQUAL_SIGN,
  1158. DOT,
  1159. COLON,
  1160. SPACE,
  1161. ],
  1162. CONTAINS: [BRACKET.PARENTHESES.LEFT, SINGLE_QUOTE, DOUBLE_QUOTE],
  1163. };
  1164. const isSupportedPseudoClass = (tokenValue) => {
  1165. return SUPPORTED_PSEUDO_CLASSES.includes(tokenValue);
  1166. };
  1167. const isOptimizationPseudoClass = (name) => {
  1168. return OPTIMIZATION_PSEUDO_CLASSES.includes(name);
  1169. };
  1170. const doesRegularContinueAfterSpace = (nextTokenType, nextTokenValue) => {
  1171. if (!nextTokenType || !nextTokenValue) {
  1172. return false;
  1173. }
  1174. return (
  1175. COMBINATORS.includes(nextTokenValue) ||
  1176. nextTokenType === TOKEN_TYPE.WORD ||
  1177. nextTokenValue === ASTERISK ||
  1178. nextTokenValue === ID_MARKER ||
  1179. nextTokenValue === CLASS_MARKER ||
  1180. nextTokenValue === COLON ||
  1181. nextTokenValue === SINGLE_QUOTE ||
  1182. nextTokenValue === DOUBLE_QUOTE ||
  1183. nextTokenValue === BRACKET.SQUARE.LEFT
  1184. );
  1185. };
  1186. const isRegexpOpening = (context, prevTokenValue, bufferNodeValue) => {
  1187. const lastExtendedPseudoClassName = getLast(
  1188. context.extendedPseudoNamesStack
  1189. );
  1190. if (!lastExtendedPseudoClassName) {
  1191. throw new Error(
  1192. "Regexp pattern allowed only in arg of extended pseudo-class"
  1193. );
  1194. }
  1195. if (CONTAINS_PSEUDO_NAMES.includes(lastExtendedPseudoClassName)) {
  1196. return POSSIBLE_MARKS_BEFORE_REGEXP.CONTAINS.includes(prevTokenValue);
  1197. }
  1198. if (
  1199. prevTokenValue === SLASH &&
  1200. lastExtendedPseudoClassName !== XPATH_PSEUDO_CLASS_MARKER
  1201. ) {
  1202. const rawArgDesc = bufferNodeValue
  1203. ? `in arg part: '${bufferNodeValue}'`
  1204. : "arg";
  1205. throw new Error(
  1206. `Invalid regexp pattern for :${lastExtendedPseudoClassName}() pseudo-class ${rawArgDesc}`
  1207. );
  1208. }
  1209. return POSSIBLE_MARKS_BEFORE_REGEXP.COMMON.includes(prevTokenValue);
  1210. };
  1211. const isAttributeOpening = (tokenValue, prevTokenValue) => {
  1212. return tokenValue === BRACKET.SQUARE.LEFT && prevTokenValue !== BACKSLASH;
  1213. };
  1214. const isAttributeClosing = (context) => {
  1215. var _getPrevToLast;
  1216. if (!context.isAttributeBracketsOpen) {
  1217. return false;
  1218. }
  1219. const noSpaceAttr = context.attributeBuffer.split(SPACE).join("");
  1220. const attrTokens = tokenizeAttribute(noSpaceAttr);
  1221. const firstAttrToken = getFirst(attrTokens);
  1222. const firstAttrTokenType =
  1223. firstAttrToken === null || firstAttrToken === void 0
  1224. ? void 0
  1225. : firstAttrToken.type;
  1226. const firstAttrTokenValue =
  1227. firstAttrToken === null || firstAttrToken === void 0
  1228. ? void 0
  1229. : firstAttrToken.value;
  1230. if (
  1231. firstAttrTokenType === TOKEN_TYPE.MARK &&
  1232. firstAttrTokenValue !== BACKSLASH
  1233. ) {
  1234. throw new Error(
  1235. `'[${context.attributeBuffer}]' is not a valid attribute due to '${firstAttrTokenValue}' at start of it`
  1236. );
  1237. }
  1238. const lastAttrToken = getLast(attrTokens);
  1239. const lastAttrTokenType =
  1240. lastAttrToken === null || lastAttrToken === void 0
  1241. ? void 0
  1242. : lastAttrToken.type;
  1243. const lastAttrTokenValue =
  1244. lastAttrToken === null || lastAttrToken === void 0
  1245. ? void 0
  1246. : lastAttrToken.value;
  1247. if (lastAttrTokenValue === EQUAL_SIGN) {
  1248. throw new Error(
  1249. `'[${context.attributeBuffer}]' is not a valid attribute due to '${EQUAL_SIGN}'`
  1250. );
  1251. }
  1252. const equalSignIndex = attrTokens.findIndex((token) => {
  1253. return token.type === TOKEN_TYPE.MARK && token.value === EQUAL_SIGN;
  1254. });
  1255. const prevToLastAttrTokenValue =
  1256. (_getPrevToLast = getPrevToLast(attrTokens)) === null ||
  1257. _getPrevToLast === void 0
  1258. ? void 0
  1259. : _getPrevToLast.value;
  1260. if (equalSignIndex === -1) {
  1261. if (lastAttrTokenType === TOKEN_TYPE.WORD) {
  1262. return true;
  1263. }
  1264. return (
  1265. prevToLastAttrTokenValue === BACKSLASH &&
  1266. (lastAttrTokenValue === DOUBLE_QUOTE ||
  1267. lastAttrTokenValue === SINGLE_QUOTE)
  1268. );
  1269. }
  1270. const nextToEqualSignToken = getItemByIndex(attrTokens, equalSignIndex + 1);
  1271. const nextToEqualSignTokenValue = nextToEqualSignToken.value;
  1272. const isAttrValueQuote =
  1273. nextToEqualSignTokenValue === SINGLE_QUOTE ||
  1274. nextToEqualSignTokenValue === DOUBLE_QUOTE;
  1275. if (!isAttrValueQuote) {
  1276. if (lastAttrTokenType === TOKEN_TYPE.WORD) {
  1277. return true;
  1278. }
  1279. throw new Error(
  1280. `'[${context.attributeBuffer}]' is not a valid attribute`
  1281. );
  1282. }
  1283. if (
  1284. lastAttrTokenType === TOKEN_TYPE.WORD &&
  1285. (lastAttrTokenValue === null || lastAttrTokenValue === void 0
  1286. ? void 0
  1287. : lastAttrTokenValue.toLocaleLowerCase()) ===
  1288. ATTRIBUTE_CASE_INSENSITIVE_FLAG
  1289. ) {
  1290. return prevToLastAttrTokenValue === nextToEqualSignTokenValue;
  1291. }
  1292. return lastAttrTokenValue === nextToEqualSignTokenValue;
  1293. };
  1294. const isWhiteSpaceChar = (tokenValue) => {
  1295. if (!tokenValue) {
  1296. return false;
  1297. }
  1298. return WHITE_SPACE_CHARACTERS.includes(tokenValue);
  1299. };
  1300. const isAbsolutePseudoClass = (str) => {
  1301. return ABSOLUTE_PSEUDO_CLASSES.includes(str);
  1302. };
  1303. const isRelativePseudoClass = (str) => {
  1304. return RELATIVE_PSEUDO_CLASSES.includes(str);
  1305. };
  1306. const getBufferNode = (context) => {
  1307. if (context.pathToBufferNode.length === 0) {
  1308. return null;
  1309. }
  1310. return getLast(context.pathToBufferNode) || null;
  1311. };
  1312. const getBufferNodeParent = (context) => {
  1313. if (context.pathToBufferNode.length < 2) {
  1314. return null;
  1315. }
  1316. return getPrevToLast(context.pathToBufferNode) || null;
  1317. };
  1318. const getContextLastRegularSelectorNode = (context) => {
  1319. const bufferNode = getBufferNode(context);
  1320. if (!bufferNode) {
  1321. throw new Error("No bufferNode found");
  1322. }
  1323. if (!isSelectorNode(bufferNode)) {
  1324. throw new Error("Unsupported bufferNode type");
  1325. }
  1326. const lastRegularSelectorNode = getLastRegularChild(bufferNode.children);
  1327. context.pathToBufferNode.push(lastRegularSelectorNode);
  1328. return lastRegularSelectorNode;
  1329. };
  1330. const updateBufferNode = (context, tokenValue) => {
  1331. const bufferNode = getBufferNode(context);
  1332. if (bufferNode === null) {
  1333. throw new Error("No bufferNode to update");
  1334. }
  1335. if (isAbsolutePseudoClassNode(bufferNode)) {
  1336. bufferNode.value += tokenValue;
  1337. } else if (isRegularSelectorNode(bufferNode)) {
  1338. bufferNode.value += tokenValue;
  1339. if (context.isAttributeBracketsOpen) {
  1340. context.attributeBuffer += tokenValue;
  1341. }
  1342. } else {
  1343. throw new Error(
  1344. `${bufferNode.type} node cannot be updated. Only RegularSelector and AbsolutePseudoClass are supported`
  1345. );
  1346. }
  1347. };
  1348. const addSelectorListNode = (context) => {
  1349. const selectorListNode = new AnySelectorNode(NODE.SELECTOR_LIST);
  1350. context.ast = selectorListNode;
  1351. context.pathToBufferNode.push(selectorListNode);
  1352. };
  1353. const addAstNodeByType = function (context, type) {
  1354. let tokenValue =
  1355. arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "";
  1356. const bufferNode = getBufferNode(context);
  1357. if (bufferNode === null) {
  1358. throw new Error("No buffer node");
  1359. }
  1360. let node;
  1361. if (type === NODE.REGULAR_SELECTOR) {
  1362. node = new RegularSelectorNode(tokenValue);
  1363. } else if (type === NODE.ABSOLUTE_PSEUDO_CLASS) {
  1364. node = new AbsolutePseudoClassNode(tokenValue);
  1365. } else if (type === NODE.RELATIVE_PSEUDO_CLASS) {
  1366. node = new RelativePseudoClassNode(tokenValue);
  1367. } else {
  1368. node = new AnySelectorNode(type);
  1369. }
  1370. bufferNode.addChild(node);
  1371. context.pathToBufferNode.push(node);
  1372. };
  1373. const initAst = (context, tokenValue) => {
  1374. addSelectorListNode(context);
  1375. addAstNodeByType(context, NODE.SELECTOR);
  1376. addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
  1377. };
  1378. const initRelativeSubtree = function (context) {
  1379. let tokenValue =
  1380. arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
  1381. addAstNodeByType(context, NODE.SELECTOR_LIST);
  1382. addAstNodeByType(context, NODE.SELECTOR);
  1383. addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
  1384. };
  1385. const upToClosest = (context, parentType) => {
  1386. for (let i = context.pathToBufferNode.length - 1; i >= 0; i -= 1) {
  1387. var _context$pathToBuffer;
  1388. if (
  1389. ((_context$pathToBuffer = context.pathToBufferNode[i]) === null ||
  1390. _context$pathToBuffer === void 0
  1391. ? void 0
  1392. : _context$pathToBuffer.type) === parentType
  1393. ) {
  1394. context.pathToBufferNode = context.pathToBufferNode.slice(0, i + 1);
  1395. break;
  1396. }
  1397. }
  1398. };
  1399. const getUpdatedBufferNode = (context) => {
  1400. const bufferNode = getBufferNode(context);
  1401. if (
  1402. bufferNode &&
  1403. isSelectorListNode(bufferNode) &&
  1404. isRelativePseudoClassNode(getBufferNodeParent(context))
  1405. ) {
  1406. return bufferNode;
  1407. }
  1408. upToClosest(context, NODE.SELECTOR);
  1409. const selectorNode = getBufferNode(context);
  1410. if (!selectorNode) {
  1411. throw new Error(
  1412. "No SelectorNode, impossible to continue selector parsing by ExtendedCss"
  1413. );
  1414. }
  1415. const lastSelectorNodeChild = getLast(selectorNode.children);
  1416. const hasExtended =
  1417. lastSelectorNodeChild &&
  1418. isExtendedSelectorNode(lastSelectorNodeChild) &&
  1419. context.standardPseudoBracketsStack.length === 0;
  1420. const supposedPseudoClassNode =
  1421. hasExtended && getFirst(lastSelectorNodeChild.children);
  1422. let newNeededBufferNode = selectorNode;
  1423. if (supposedPseudoClassNode) {
  1424. const lastExtendedPseudoName =
  1425. hasExtended && supposedPseudoClassNode.name;
  1426. const isLastExtendedNameRelative =
  1427. lastExtendedPseudoName && isRelativePseudoClass(lastExtendedPseudoName);
  1428. const isLastExtendedNameAbsolute =
  1429. lastExtendedPseudoName && isAbsolutePseudoClass(lastExtendedPseudoName);
  1430. const hasRelativeExtended =
  1431. isLastExtendedNameRelative &&
  1432. context.extendedPseudoBracketsStack.length > 0 &&
  1433. context.extendedPseudoBracketsStack.length ===
  1434. context.extendedPseudoNamesStack.length;
  1435. const hasAbsoluteExtended =
  1436. isLastExtendedNameAbsolute &&
  1437. lastExtendedPseudoName === getLast(context.extendedPseudoNamesStack);
  1438. if (hasRelativeExtended) {
  1439. context.pathToBufferNode.push(lastSelectorNodeChild);
  1440. newNeededBufferNode = supposedPseudoClassNode;
  1441. } else if (hasAbsoluteExtended) {
  1442. context.pathToBufferNode.push(lastSelectorNodeChild);
  1443. newNeededBufferNode = supposedPseudoClassNode;
  1444. }
  1445. } else if (hasExtended) {
  1446. newNeededBufferNode = selectorNode;
  1447. } else {
  1448. newNeededBufferNode = getContextLastRegularSelectorNode(context);
  1449. }
  1450. context.pathToBufferNode.push(newNeededBufferNode);
  1451. return newNeededBufferNode;
  1452. };
  1453. const handleNextTokenOnColon = (
  1454. context,
  1455. selector,
  1456. tokenValue,
  1457. nextTokenValue,
  1458. nextToNextTokenValue
  1459. ) => {
  1460. if (!nextTokenValue) {
  1461. throw new Error(
  1462. `Invalid colon ':' at the end of selector: '${selector}'`
  1463. );
  1464. }
  1465. if (!isSupportedPseudoClass(nextTokenValue.toLowerCase())) {
  1466. if (nextTokenValue.toLowerCase() === REMOVE_PSEUDO_MARKER) {
  1467. throw new Error(`${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${selector}'`);
  1468. }
  1469. updateBufferNode(context, tokenValue);
  1470. if (
  1471. nextToNextTokenValue &&
  1472. nextToNextTokenValue === BRACKET.PARENTHESES.LEFT &&
  1473. !context.isAttributeBracketsOpen
  1474. ) {
  1475. context.standardPseudoNamesStack.push(nextTokenValue);
  1476. }
  1477. } else {
  1478. if (
  1479. HAS_PSEUDO_CLASS_MARKERS.includes(nextTokenValue) &&
  1480. context.standardPseudoNamesStack.length > 0
  1481. ) {
  1482. throw new Error(
  1483. `Usage of :${nextTokenValue}() pseudo-class is not allowed inside regular pseudo: '${getLast(
  1484. context.standardPseudoNamesStack
  1485. )}'`
  1486. );
  1487. } else {
  1488. upToClosest(context, NODE.SELECTOR);
  1489. addAstNodeByType(context, NODE.EXTENDED_SELECTOR);
  1490. }
  1491. }
  1492. };
  1493. const IS_OR_NOT_PSEUDO_SELECTING_ROOT = `html ${ASTERISK}`;
  1494. const hasExtendedSelector = (selectorList) => {
  1495. return selectorList.children.some((selectorNode) => {
  1496. return selectorNode.children.some((selectorNodeChild) => {
  1497. return isExtendedSelectorNode(selectorNodeChild);
  1498. });
  1499. });
  1500. };
  1501. const selectorListOfRegularsToString = (selectorList) => {
  1502. const standardCssSelectors = selectorList.children.map((selectorNode) => {
  1503. const selectorOnlyChild = getNodeOnlyChild(
  1504. selectorNode,
  1505. "Ast Selector node should have RegularSelector node"
  1506. );
  1507. return getNodeValue(selectorOnlyChild);
  1508. });
  1509. return standardCssSelectors.join(`${COMMA}${SPACE}`);
  1510. };
  1511. const updateNodeChildren = (node, newChildren) => {
  1512. node.children = newChildren;
  1513. return node;
  1514. };
  1515. const shouldOptimizeExtendedSelector = (currExtendedSelectorNode) => {
  1516. if (currExtendedSelectorNode === null) {
  1517. return false;
  1518. }
  1519. const extendedPseudoClassNode = getPseudoClassNode(
  1520. currExtendedSelectorNode
  1521. );
  1522. const pseudoName = getNodeName(extendedPseudoClassNode);
  1523. if (isAbsolutePseudoClass(pseudoName)) {
  1524. return false;
  1525. }
  1526. const relativeSelectorList = getRelativeSelectorListNode(
  1527. extendedPseudoClassNode
  1528. );
  1529. const innerSelectorNodes = relativeSelectorList.children;
  1530. if (isOptimizationPseudoClass(pseudoName)) {
  1531. const areAllSelectorNodeChildrenRegular = innerSelectorNodes.every(
  1532. (selectorNode) => {
  1533. try {
  1534. const selectorOnlyChild = getNodeOnlyChild(
  1535. selectorNode,
  1536. "Selector node should have RegularSelector"
  1537. );
  1538. return isRegularSelectorNode(selectorOnlyChild);
  1539. } catch (e) {
  1540. return false;
  1541. }
  1542. }
  1543. );
  1544. if (areAllSelectorNodeChildrenRegular) {
  1545. return true;
  1546. }
  1547. }
  1548. return innerSelectorNodes.some((selectorNode) => {
  1549. return selectorNode.children.some((selectorNodeChild) => {
  1550. if (!isExtendedSelectorNode(selectorNodeChild)) {
  1551. return false;
  1552. }
  1553. return shouldOptimizeExtendedSelector(selectorNodeChild);
  1554. });
  1555. });
  1556. };
  1557. const getOptimizedExtendedSelector = (
  1558. currExtendedSelectorNode,
  1559. prevRegularSelectorNode
  1560. ) => {
  1561. if (!currExtendedSelectorNode) {
  1562. return null;
  1563. }
  1564. const extendedPseudoClassNode = getPseudoClassNode(
  1565. currExtendedSelectorNode
  1566. );
  1567. const relativeSelectorList = getRelativeSelectorListNode(
  1568. extendedPseudoClassNode
  1569. );
  1570. const hasInnerExtendedSelector = hasExtendedSelector(relativeSelectorList);
  1571. if (!hasInnerExtendedSelector) {
  1572. const relativeSelectorListStr =
  1573. selectorListOfRegularsToString(relativeSelectorList);
  1574. const pseudoName = getNodeName(extendedPseudoClassNode);
  1575. const optimizedExtendedStr = `${COLON}${pseudoName}${BRACKET.PARENTHESES.LEFT}${relativeSelectorListStr}${BRACKET.PARENTHESES.RIGHT}`;
  1576. prevRegularSelectorNode.value = `${getNodeValue(
  1577. prevRegularSelectorNode
  1578. )}${optimizedExtendedStr}`;
  1579. return null;
  1580. }
  1581. const optimizedRelativeSelectorList =
  1582. optimizeSelectorListNode(relativeSelectorList);
  1583. const optimizedExtendedPseudoClassNode = updateNodeChildren(
  1584. extendedPseudoClassNode,
  1585. [optimizedRelativeSelectorList]
  1586. );
  1587. return updateNodeChildren(currExtendedSelectorNode, [
  1588. optimizedExtendedPseudoClassNode,
  1589. ]);
  1590. };
  1591. const optimizeCurrentRegularSelector = (current, previous) => {
  1592. previous.value = `${getNodeValue(previous)}${SPACE}${getNodeValue(
  1593. current
  1594. )}`;
  1595. };
  1596. const optimizeSelectorNode = (selectorNode) => {
  1597. const rawSelectorNodeChildren = selectorNode.children;
  1598. const optimizedChildrenList = [];
  1599. let currentIndex = 0;
  1600. while (currentIndex < rawSelectorNodeChildren.length) {
  1601. const currentChild = getItemByIndex(
  1602. rawSelectorNodeChildren,
  1603. currentIndex,
  1604. "currentChild should be specified"
  1605. );
  1606. if (currentIndex === 0) {
  1607. optimizedChildrenList.push(currentChild);
  1608. } else {
  1609. const prevRegularChild = getLastRegularChild(optimizedChildrenList);
  1610. if (isExtendedSelectorNode(currentChild)) {
  1611. let optimizedExtendedSelector = null;
  1612. let isOptimizationNeeded =
  1613. shouldOptimizeExtendedSelector(currentChild);
  1614. optimizedExtendedSelector = currentChild;
  1615. while (isOptimizationNeeded) {
  1616. optimizedExtendedSelector = getOptimizedExtendedSelector(
  1617. optimizedExtendedSelector,
  1618. prevRegularChild
  1619. );
  1620. isOptimizationNeeded = shouldOptimizeExtendedSelector(
  1621. optimizedExtendedSelector
  1622. );
  1623. }
  1624. if (optimizedExtendedSelector !== null) {
  1625. optimizedChildrenList.push(optimizedExtendedSelector);
  1626. const optimizedPseudoClass = getPseudoClassNode(
  1627. optimizedExtendedSelector
  1628. );
  1629. const optimizedPseudoName = getNodeName(optimizedPseudoClass);
  1630. if (
  1631. getNodeValue(prevRegularChild) === ASTERISK &&
  1632. isOptimizationPseudoClass(optimizedPseudoName)
  1633. ) {
  1634. prevRegularChild.value = IS_OR_NOT_PSEUDO_SELECTING_ROOT;
  1635. }
  1636. }
  1637. } else if (isRegularSelectorNode(currentChild)) {
  1638. const lastOptimizedChild = getLast(optimizedChildrenList) || null;
  1639. if (isRegularSelectorNode(lastOptimizedChild)) {
  1640. optimizeCurrentRegularSelector(currentChild, prevRegularChild);
  1641. }
  1642. }
  1643. }
  1644. currentIndex += 1;
  1645. }
  1646. return updateNodeChildren(selectorNode, optimizedChildrenList);
  1647. };
  1648. const optimizeSelectorListNode = (selectorListNode) => {
  1649. return updateNodeChildren(
  1650. selectorListNode,
  1651. selectorListNode.children.map((s) => optimizeSelectorNode(s))
  1652. );
  1653. };
  1654. const optimizeAst = (ast) => {
  1655. return optimizeSelectorListNode(ast);
  1656. };
  1657. const XPATH_PSEUDO_SELECTING_ROOT = "body";
  1658. const NO_WHITESPACE_ERROR_PREFIX =
  1659. "No white space is allowed before or after extended pseudo-class name in selector";
  1660. const parse = (selector) => {
  1661. const tokens = tokenizeSelector(selector);
  1662. const context = {
  1663. ast: null,
  1664. pathToBufferNode: [],
  1665. extendedPseudoNamesStack: [],
  1666. extendedPseudoBracketsStack: [],
  1667. standardPseudoNamesStack: [],
  1668. standardPseudoBracketsStack: [],
  1669. isAttributeBracketsOpen: false,
  1670. attributeBuffer: "",
  1671. isRegexpOpen: false,
  1672. shouldOptimize: false,
  1673. };
  1674. let i = 0;
  1675. while (i < tokens.length) {
  1676. const token = tokens[i];
  1677. if (!token) {
  1678. break;
  1679. }
  1680. const { type: tokenType, value: tokenValue } = token;
  1681. const nextToken = tokens[i + 1];
  1682. const nextTokenType =
  1683. nextToken === null || nextToken === void 0 ? void 0 : nextToken.type;
  1684. const nextTokenValue =
  1685. nextToken === null || nextToken === void 0 ? void 0 : nextToken.value;
  1686. const nextToNextToken = tokens[i + 2];
  1687. const nextToNextTokenValue =
  1688. nextToNextToken === null || nextToNextToken === void 0
  1689. ? void 0
  1690. : nextToNextToken.value;
  1691. const previousToken = tokens[i - 1];
  1692. const prevTokenType =
  1693. previousToken === null || previousToken === void 0
  1694. ? void 0
  1695. : previousToken.type;
  1696. const prevTokenValue =
  1697. previousToken === null || previousToken === void 0
  1698. ? void 0
  1699. : previousToken.value;
  1700. const previousToPreviousToken = tokens[i - 2];
  1701. const prevToPrevTokenValue =
  1702. previousToPreviousToken === null || previousToPreviousToken === void 0
  1703. ? void 0
  1704. : previousToPreviousToken.value;
  1705. let bufferNode = getBufferNode(context);
  1706. switch (tokenType) {
  1707. case TOKEN_TYPE.WORD:
  1708. if (bufferNode === null) {
  1709. initAst(context, tokenValue);
  1710. } else if (isSelectorListNode(bufferNode)) {
  1711. addAstNodeByType(context, NODE.SELECTOR);
  1712. addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
  1713. } else if (isRegularSelectorNode(bufferNode)) {
  1714. updateBufferNode(context, tokenValue);
  1715. } else if (isExtendedSelectorNode(bufferNode)) {
  1716. if (
  1717. isWhiteSpaceChar(nextTokenValue) &&
  1718. nextToNextTokenValue === BRACKET.PARENTHESES.LEFT
  1719. ) {
  1720. throw new Error(`${NO_WHITESPACE_ERROR_PREFIX}: '${selector}'`);
  1721. }
  1722. const lowerCaseTokenValue = tokenValue.toLowerCase();
  1723. context.extendedPseudoNamesStack.push(lowerCaseTokenValue);
  1724. if (isAbsolutePseudoClass(lowerCaseTokenValue)) {
  1725. addAstNodeByType(
  1726. context,
  1727. NODE.ABSOLUTE_PSEUDO_CLASS,
  1728. lowerCaseTokenValue
  1729. );
  1730. } else {
  1731. addAstNodeByType(
  1732. context,
  1733. NODE.RELATIVE_PSEUDO_CLASS,
  1734. lowerCaseTokenValue
  1735. );
  1736. if (isOptimizationPseudoClass(lowerCaseTokenValue)) {
  1737. context.shouldOptimize = true;
  1738. }
  1739. }
  1740. } else if (isAbsolutePseudoClassNode(bufferNode)) {
  1741. updateBufferNode(context, tokenValue);
  1742. } else if (isRelativePseudoClassNode(bufferNode)) {
  1743. initRelativeSubtree(context, tokenValue);
  1744. }
  1745. break;
  1746. case TOKEN_TYPE.MARK:
  1747. switch (tokenValue) {
  1748. case COMMA:
  1749. if (
  1750. !bufferNode ||
  1751. (typeof bufferNode !== "undefined" && !nextTokenValue)
  1752. ) {
  1753. throw new Error(`'${selector}' is not a valid selector`);
  1754. } else if (isRegularSelectorNode(bufferNode)) {
  1755. if (context.isAttributeBracketsOpen) {
  1756. updateBufferNode(context, tokenValue);
  1757. } else {
  1758. upToClosest(context, NODE.SELECTOR_LIST);
  1759. }
  1760. } else if (isAbsolutePseudoClassNode(bufferNode)) {
  1761. updateBufferNode(context, tokenValue);
  1762. } else if (isSelectorNode(bufferNode)) {
  1763. upToClosest(context, NODE.SELECTOR_LIST);
  1764. }
  1765. break;
  1766. case SPACE:
  1767. if (
  1768. isRegularSelectorNode(bufferNode) &&
  1769. !context.isAttributeBracketsOpen
  1770. ) {
  1771. bufferNode = getUpdatedBufferNode(context);
  1772. }
  1773. if (isRegularSelectorNode(bufferNode)) {
  1774. if (
  1775. !context.isAttributeBracketsOpen &&
  1776. ((prevTokenValue === COLON &&
  1777. nextTokenType === TOKEN_TYPE.WORD) ||
  1778. (prevTokenType === TOKEN_TYPE.WORD &&
  1779. nextTokenValue === BRACKET.PARENTHESES.LEFT))
  1780. ) {
  1781. throw new Error(`'${selector}' is not a valid selector`);
  1782. }
  1783. if (
  1784. !nextTokenValue ||
  1785. doesRegularContinueAfterSpace(
  1786. nextTokenType,
  1787. nextTokenValue
  1788. ) ||
  1789. context.isAttributeBracketsOpen
  1790. ) {
  1791. updateBufferNode(context, tokenValue);
  1792. }
  1793. }
  1794. if (isAbsolutePseudoClassNode(bufferNode)) {
  1795. updateBufferNode(context, tokenValue);
  1796. }
  1797. if (isRelativePseudoClassNode(bufferNode)) {
  1798. initRelativeSubtree(context);
  1799. }
  1800. if (isSelectorNode(bufferNode)) {
  1801. if (
  1802. doesRegularContinueAfterSpace(nextTokenType, nextTokenValue)
  1803. ) {
  1804. addAstNodeByType(context, NODE.REGULAR_SELECTOR);
  1805. }
  1806. }
  1807. break;
  1808. case DESCENDANT_COMBINATOR:
  1809. case CHILD_COMBINATOR:
  1810. case NEXT_SIBLING_COMBINATOR:
  1811. case SUBSEQUENT_SIBLING_COMBINATOR:
  1812. case SEMICOLON:
  1813. case SLASH:
  1814. case BACKSLASH:
  1815. case SINGLE_QUOTE:
  1816. case DOUBLE_QUOTE:
  1817. case CARET:
  1818. case DOLLAR_SIGN:
  1819. case BRACKET.CURLY.LEFT:
  1820. case BRACKET.CURLY.RIGHT:
  1821. case ASTERISK:
  1822. case ID_MARKER:
  1823. case CLASS_MARKER:
  1824. case BRACKET.SQUARE.LEFT:
  1825. if (COMBINATORS.includes(tokenValue)) {
  1826. if (bufferNode === null) {
  1827. throw new Error(`'${selector}' is not a valid selector`);
  1828. }
  1829. bufferNode = getUpdatedBufferNode(context);
  1830. }
  1831. if (bufferNode === null) {
  1832. initAst(context, tokenValue);
  1833. if (isAttributeOpening(tokenValue, prevTokenValue)) {
  1834. context.isAttributeBracketsOpen = true;
  1835. }
  1836. } else if (isRegularSelectorNode(bufferNode)) {
  1837. if (
  1838. tokenValue === BRACKET.CURLY.LEFT &&
  1839. !(context.isAttributeBracketsOpen || context.isRegexpOpen)
  1840. ) {
  1841. throw new Error(`'${selector}' is not a valid selector`);
  1842. }
  1843. updateBufferNode(context, tokenValue);
  1844. if (isAttributeOpening(tokenValue, prevTokenValue)) {
  1845. context.isAttributeBracketsOpen = true;
  1846. }
  1847. } else if (isAbsolutePseudoClassNode(bufferNode)) {
  1848. updateBufferNode(context, tokenValue);
  1849. if (
  1850. tokenValue === SLASH &&
  1851. context.extendedPseudoNamesStack.length > 0
  1852. ) {
  1853. if (
  1854. prevTokenValue === SLASH &&
  1855. prevToPrevTokenValue === BACKSLASH
  1856. ) {
  1857. context.isRegexpOpen = false;
  1858. } else if (prevTokenValue && prevTokenValue !== BACKSLASH) {
  1859. if (
  1860. isRegexpOpening(
  1861. context,
  1862. prevTokenValue,
  1863. getNodeValue(bufferNode)
  1864. )
  1865. ) {
  1866. context.isRegexpOpen = !context.isRegexpOpen;
  1867. } else {
  1868. context.isRegexpOpen = false;
  1869. }
  1870. }
  1871. }
  1872. } else if (isRelativePseudoClassNode(bufferNode)) {
  1873. initRelativeSubtree(context, tokenValue);
  1874. if (isAttributeOpening(tokenValue, prevTokenValue)) {
  1875. context.isAttributeBracketsOpen = true;
  1876. }
  1877. } else if (isSelectorNode(bufferNode)) {
  1878. if (COMBINATORS.includes(tokenValue)) {
  1879. addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
  1880. } else if (!context.isRegexpOpen) {
  1881. bufferNode = getContextLastRegularSelectorNode(context);
  1882. updateBufferNode(context, tokenValue);
  1883. if (isAttributeOpening(tokenValue, prevTokenValue)) {
  1884. context.isAttributeBracketsOpen = true;
  1885. }
  1886. }
  1887. } else if (isSelectorListNode(bufferNode)) {
  1888. addAstNodeByType(context, NODE.SELECTOR);
  1889. addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
  1890. if (isAttributeOpening(tokenValue, prevTokenValue)) {
  1891. context.isAttributeBracketsOpen = true;
  1892. }
  1893. }
  1894. break;
  1895. case BRACKET.SQUARE.RIGHT:
  1896. if (isRegularSelectorNode(bufferNode)) {
  1897. if (
  1898. !context.isAttributeBracketsOpen &&
  1899. prevTokenValue !== BACKSLASH
  1900. ) {
  1901. throw new Error(
  1902. `'${selector}' is not a valid selector due to '${tokenValue}' after '${getNodeValue(
  1903. bufferNode
  1904. )}'`
  1905. );
  1906. }
  1907. if (isAttributeClosing(context)) {
  1908. context.isAttributeBracketsOpen = false;
  1909. context.attributeBuffer = "";
  1910. }
  1911. updateBufferNode(context, tokenValue);
  1912. }
  1913. if (isAbsolutePseudoClassNode(bufferNode)) {
  1914. updateBufferNode(context, tokenValue);
  1915. }
  1916. break;
  1917. case COLON:
  1918. if (
  1919. isWhiteSpaceChar(nextTokenValue) &&
  1920. nextToNextTokenValue &&
  1921. SUPPORTED_PSEUDO_CLASSES.includes(nextToNextTokenValue)
  1922. ) {
  1923. throw new Error(`${NO_WHITESPACE_ERROR_PREFIX}: '${selector}'`);
  1924. }
  1925. if (bufferNode === null) {
  1926. if (nextTokenValue === XPATH_PSEUDO_CLASS_MARKER) {
  1927. initAst(context, XPATH_PSEUDO_SELECTING_ROOT);
  1928. } else if (
  1929. nextTokenValue === UPWARD_PSEUDO_CLASS_MARKER ||
  1930. nextTokenValue === NTH_ANCESTOR_PSEUDO_CLASS_MARKER
  1931. ) {
  1932. throw new Error(
  1933. `${NO_SELECTOR_ERROR_PREFIX} before :${nextTokenValue}() pseudo-class`
  1934. );
  1935. } else {
  1936. initAst(context, ASTERISK);
  1937. }
  1938. bufferNode = getBufferNode(context);
  1939. }
  1940. if (isSelectorListNode(bufferNode)) {
  1941. addAstNodeByType(context, NODE.SELECTOR);
  1942. addAstNodeByType(context, NODE.REGULAR_SELECTOR);
  1943. bufferNode = getBufferNode(context);
  1944. }
  1945. if (isRegularSelectorNode(bufferNode)) {
  1946. if (
  1947. (prevTokenValue && COMBINATORS.includes(prevTokenValue)) ||
  1948. prevTokenValue === COMMA
  1949. ) {
  1950. updateBufferNode(context, ASTERISK);
  1951. }
  1952. handleNextTokenOnColon(
  1953. context,
  1954. selector,
  1955. tokenValue,
  1956. nextTokenValue,
  1957. nextToNextTokenValue
  1958. );
  1959. }
  1960. if (isSelectorNode(bufferNode)) {
  1961. if (!nextTokenValue) {
  1962. throw new Error(
  1963. `Invalid colon ':' at the end of selector: '${selector}'`
  1964. );
  1965. }
  1966. if (isSupportedPseudoClass(nextTokenValue.toLowerCase())) {
  1967. addAstNodeByType(context, NODE.EXTENDED_SELECTOR);
  1968. } else if (
  1969. nextTokenValue.toLowerCase() === REMOVE_PSEUDO_MARKER
  1970. ) {
  1971. throw new Error(
  1972. `${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${selector}'`
  1973. );
  1974. } else {
  1975. bufferNode = getContextLastRegularSelectorNode(context);
  1976. handleNextTokenOnColon(
  1977. context,
  1978. selector,
  1979. tokenValue,
  1980. nextTokenType,
  1981. nextToNextTokenValue
  1982. );
  1983. }
  1984. }
  1985. if (isAbsolutePseudoClassNode(bufferNode)) {
  1986. if (
  1987. getNodeName(bufferNode) === XPATH_PSEUDO_CLASS_MARKER &&
  1988. nextTokenValue &&
  1989. SUPPORTED_PSEUDO_CLASSES.includes(nextTokenValue) &&
  1990. nextToNextTokenValue === BRACKET.PARENTHESES.LEFT
  1991. ) {
  1992. throw new Error(
  1993. `:xpath() pseudo-class should be the last in selector: '${selector}'`
  1994. );
  1995. }
  1996. updateBufferNode(context, tokenValue);
  1997. }
  1998. if (isRelativePseudoClassNode(bufferNode)) {
  1999. if (!nextTokenValue) {
  2000. throw new Error(
  2001. `Invalid pseudo-class arg at the end of selector: '${selector}'`
  2002. );
  2003. }
  2004. initRelativeSubtree(context, ASTERISK);
  2005. if (!isSupportedPseudoClass(nextTokenValue.toLowerCase())) {
  2006. updateBufferNode(context, tokenValue);
  2007. if (nextToNextTokenValue === BRACKET.PARENTHESES.LEFT) {
  2008. context.standardPseudoNamesStack.push(nextTokenValue);
  2009. }
  2010. } else {
  2011. upToClosest(context, NODE.SELECTOR);
  2012. addAstNodeByType(context, NODE.EXTENDED_SELECTOR);
  2013. }
  2014. }
  2015. break;
  2016. case BRACKET.PARENTHESES.LEFT:
  2017. if (isAbsolutePseudoClassNode(bufferNode)) {
  2018. if (
  2019. getNodeName(bufferNode) !== XPATH_PSEUDO_CLASS_MARKER &&
  2020. context.isRegexpOpen
  2021. ) {
  2022. updateBufferNode(context, tokenValue);
  2023. } else {
  2024. context.extendedPseudoBracketsStack.push(tokenValue);
  2025. if (
  2026. context.extendedPseudoBracketsStack.length >
  2027. context.extendedPseudoNamesStack.length
  2028. ) {
  2029. updateBufferNode(context, tokenValue);
  2030. }
  2031. }
  2032. }
  2033. if (isRegularSelectorNode(bufferNode)) {
  2034. if (context.standardPseudoNamesStack.length > 0) {
  2035. updateBufferNode(context, tokenValue);
  2036. context.standardPseudoBracketsStack.push(tokenValue);
  2037. }
  2038. if (context.isAttributeBracketsOpen) {
  2039. updateBufferNode(context, tokenValue);
  2040. }
  2041. }
  2042. if (isRelativePseudoClassNode(bufferNode)) {
  2043. context.extendedPseudoBracketsStack.push(tokenValue);
  2044. }
  2045. break;
  2046. case BRACKET.PARENTHESES.RIGHT:
  2047. if (isAbsolutePseudoClassNode(bufferNode)) {
  2048. if (
  2049. getNodeName(bufferNode) !== XPATH_PSEUDO_CLASS_MARKER &&
  2050. context.isRegexpOpen
  2051. ) {
  2052. updateBufferNode(context, tokenValue);
  2053. } else {
  2054. context.extendedPseudoBracketsStack.pop();
  2055. if (getNodeName(bufferNode) !== XPATH_PSEUDO_CLASS_MARKER) {
  2056. context.extendedPseudoNamesStack.pop();
  2057. if (
  2058. context.extendedPseudoBracketsStack.length >
  2059. context.extendedPseudoNamesStack.length
  2060. ) {
  2061. updateBufferNode(context, tokenValue);
  2062. } else if (
  2063. context.extendedPseudoBracketsStack.length >= 0 &&
  2064. context.extendedPseudoNamesStack.length >= 0
  2065. ) {
  2066. upToClosest(context, NODE.SELECTOR);
  2067. }
  2068. } else {
  2069. if (
  2070. context.extendedPseudoBracketsStack.length <
  2071. context.extendedPseudoNamesStack.length
  2072. ) {
  2073. context.extendedPseudoNamesStack.pop();
  2074. } else {
  2075. updateBufferNode(context, tokenValue);
  2076. }
  2077. }
  2078. }
  2079. }
  2080. if (isRegularSelectorNode(bufferNode)) {
  2081. if (context.isAttributeBracketsOpen) {
  2082. updateBufferNode(context, tokenValue);
  2083. } else if (
  2084. context.standardPseudoNamesStack.length > 0 &&
  2085. context.standardPseudoBracketsStack.length > 0
  2086. ) {
  2087. updateBufferNode(context, tokenValue);
  2088. context.standardPseudoBracketsStack.pop();
  2089. const lastStandardPseudo =
  2090. context.standardPseudoNamesStack.pop();
  2091. if (!lastStandardPseudo) {
  2092. throw new Error(
  2093. `Parsing error. Invalid selector: ${selector}`
  2094. );
  2095. }
  2096. if (
  2097. Object.values(REGULAR_PSEUDO_ELEMENTS).includes(
  2098. lastStandardPseudo
  2099. ) &&
  2100. nextTokenValue === COLON &&
  2101. nextToNextTokenValue &&
  2102. HAS_PSEUDO_CLASS_MARKERS.includes(nextToNextTokenValue)
  2103. ) {
  2104. throw new Error(
  2105. `Usage of :${nextToNextTokenValue}() pseudo-class is not allowed after any regular pseudo-element: '${lastStandardPseudo}'`
  2106. );
  2107. }
  2108. } else {
  2109. context.extendedPseudoBracketsStack.pop();
  2110. context.extendedPseudoNamesStack.pop();
  2111. upToClosest(context, NODE.EXTENDED_SELECTOR);
  2112. upToClosest(context, NODE.SELECTOR);
  2113. }
  2114. }
  2115. if (isSelectorNode(bufferNode)) {
  2116. context.extendedPseudoBracketsStack.pop();
  2117. context.extendedPseudoNamesStack.pop();
  2118. upToClosest(context, NODE.EXTENDED_SELECTOR);
  2119. upToClosest(context, NODE.SELECTOR);
  2120. }
  2121. if (isRelativePseudoClassNode(bufferNode)) {
  2122. if (
  2123. context.extendedPseudoNamesStack.length > 0 &&
  2124. context.extendedPseudoBracketsStack.length > 0
  2125. ) {
  2126. context.extendedPseudoBracketsStack.pop();
  2127. context.extendedPseudoNamesStack.pop();
  2128. }
  2129. }
  2130. break;
  2131. case LINE_FEED:
  2132. case FORM_FEED:
  2133. case CARRIAGE_RETURN:
  2134. throw new Error(`'${selector}' is not a valid selector`);
  2135. case TAB:
  2136. if (
  2137. isRegularSelectorNode(bufferNode) &&
  2138. context.isAttributeBracketsOpen
  2139. ) {
  2140. updateBufferNode(context, tokenValue);
  2141. } else {
  2142. throw new Error(`'${selector}' is not a valid selector`);
  2143. }
  2144. }
  2145. break;
  2146. default:
  2147. throw new Error(`Unknown type of token: '${tokenValue}'`);
  2148. }
  2149. i += 1;
  2150. }
  2151. if (context.ast === null) {
  2152. throw new Error(`'${selector}' is not a valid selector`);
  2153. }
  2154. if (
  2155. context.extendedPseudoNamesStack.length > 0 ||
  2156. context.extendedPseudoBracketsStack.length > 0
  2157. ) {
  2158. throw new Error(
  2159. `Unbalanced brackets for extended pseudo-class: '${getLast(
  2160. context.extendedPseudoNamesStack
  2161. )}'`
  2162. );
  2163. }
  2164. if (context.isAttributeBracketsOpen) {
  2165. throw new Error(
  2166. `Unbalanced attribute brackets in selector: '${selector}'`
  2167. );
  2168. }
  2169. return context.shouldOptimize ? optimizeAst(context.ast) : context.ast;
  2170. };
  2171. const natives = {
  2172. MutationObserver: window.MutationObserver || window.WebKitMutationObserver,
  2173. };
  2174. class NativeTextContent {
  2175. constructor() {
  2176. this.nativeNode = window.Node || Node;
  2177. }
  2178. setGetter() {
  2179. var _Object$getOwnPropert;
  2180. this.getter =
  2181. (_Object$getOwnPropert = Object.getOwnPropertyDescriptor(
  2182. this.nativeNode.prototype,
  2183. "textContent"
  2184. )) === null || _Object$getOwnPropert === void 0
  2185. ? void 0
  2186. : _Object$getOwnPropert.get;
  2187. }
  2188. }
  2189. const nativeTextContent = new NativeTextContent();
  2190. const getNodeTextContent = (domElement) => {
  2191. if (nativeTextContent.getter) {
  2192. return nativeTextContent.getter.apply(domElement);
  2193. }
  2194. return domElement.textContent || "";
  2195. };
  2196. const getElementSelectorDesc = (element) => {
  2197. let selectorText = element.tagName.toLowerCase();
  2198. selectorText += Array.from(element.attributes)
  2199. .map((attr) => {
  2200. return `[${attr.name}="${element.getAttribute(attr.name)}"]`;
  2201. })
  2202. .join("");
  2203. return selectorText;
  2204. };
  2205. const getElementSelectorPath = (inputEl) => {
  2206. if (!(inputEl instanceof Element)) {
  2207. throw new Error("Function received argument with wrong type");
  2208. }
  2209. let el;
  2210. el = inputEl;
  2211. const path = [];
  2212. while (!!el && el.nodeType === Node.ELEMENT_NODE) {
  2213. let selector = el.nodeName.toLowerCase();
  2214. if (el.id && typeof el.id === "string") {
  2215. selector += `#${el.id}`;
  2216. path.unshift(selector);
  2217. break;
  2218. }
  2219. let sibling = el;
  2220. let nth = 1;
  2221. while (sibling.previousElementSibling) {
  2222. sibling = sibling.previousElementSibling;
  2223. if (
  2224. sibling.nodeType === Node.ELEMENT_NODE &&
  2225. sibling.nodeName.toLowerCase() === selector
  2226. ) {
  2227. nth += 1;
  2228. }
  2229. }
  2230. if (nth !== 1) {
  2231. selector += `:nth-of-type(${nth})`;
  2232. }
  2233. path.unshift(selector);
  2234. el = el.parentElement;
  2235. }
  2236. return path.join(" > ");
  2237. };
  2238. const isHtmlElement = (element) => {
  2239. return element instanceof HTMLElement;
  2240. };
  2241. const getParent = (element, errorMessage) => {
  2242. const { parentElement } = element;
  2243. if (!parentElement) {
  2244. throw new Error(errorMessage || "Element does no have parent element");
  2245. }
  2246. return parentElement;
  2247. };
  2248. const isErrorWithMessage = (error) => {
  2249. return (
  2250. typeof error === "object" &&
  2251. error !== null &&
  2252. "message" in error &&
  2253. typeof error.message === "string"
  2254. );
  2255. };
  2256. const toErrorWithMessage = (maybeError) => {
  2257. if (isErrorWithMessage(maybeError)) {
  2258. return maybeError;
  2259. }
  2260. try {
  2261. return new Error(JSON.stringify(maybeError));
  2262. } catch {
  2263. return new Error(String(maybeError));
  2264. }
  2265. };
  2266. const getErrorMessage = (error) => {
  2267. return toErrorWithMessage(error).message;
  2268. };
  2269. const logger = {
  2270. error:
  2271. typeof console !== "undefined" && console.error && console.error.bind
  2272. ? console.error.bind(window.console)
  2273. : console.error,
  2274. info:
  2275. typeof console !== "undefined" && console.info && console.info.bind
  2276. ? console.info.bind(window.console)
  2277. : console.info,
  2278. };
  2279. const removeSuffix = (str, suffix) => {
  2280. const index = str.indexOf(suffix, str.length - suffix.length);
  2281. if (index >= 0) {
  2282. return str.substring(0, index);
  2283. }
  2284. return str;
  2285. };
  2286. const replaceAll = (input, pattern, replacement) => {
  2287. if (!input) {
  2288. return input;
  2289. }
  2290. return input.split(pattern).join(replacement);
  2291. };
  2292. const toRegExp = (str) => {
  2293. if (str.startsWith(SLASH) && str.endsWith(SLASH)) {
  2294. return new RegExp(str.slice(1, -1));
  2295. }
  2296. const escaped = str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  2297. return new RegExp(escaped);
  2298. };
  2299. const convertTypeIntoString = (value) => {
  2300. let output;
  2301. switch (value) {
  2302. case undefined:
  2303. output = "undefined";
  2304. break;
  2305. case null:
  2306. output = "null";
  2307. break;
  2308. default:
  2309. output = value.toString();
  2310. }
  2311. return output;
  2312. };
  2313. const convertTypeFromString = (value) => {
  2314. const numValue = Number(value);
  2315. let output;
  2316. if (!Number.isNaN(numValue)) {
  2317. output = numValue;
  2318. } else {
  2319. switch (value) {
  2320. case "undefined":
  2321. output = undefined;
  2322. break;
  2323. case "null":
  2324. output = null;
  2325. break;
  2326. case "true":
  2327. output = true;
  2328. break;
  2329. case "false":
  2330. output = false;
  2331. break;
  2332. default:
  2333. output = value;
  2334. }
  2335. }
  2336. return output;
  2337. };
  2338. const SAFARI_USER_AGENT_REGEXP = /\sVersion\/(\d{2}\.\d)(.+\s|\s)(Safari)\//;
  2339. const isSafariBrowser = SAFARI_USER_AGENT_REGEXP.test(navigator.userAgent);
  2340. const isUserAgentSupported = (userAgent) => {
  2341. if (userAgent.includes("MSIE") || userAgent.includes("Trident/")) {
  2342. return false;
  2343. }
  2344. return true;
  2345. };
  2346. const isBrowserSupported = () => {
  2347. return isUserAgentSupported(navigator.userAgent);
  2348. };
  2349. const CSS_PROPERTY = {
  2350. BACKGROUND: "background",
  2351. BACKGROUND_IMAGE: "background-image",
  2352. CONTENT: "content",
  2353. OPACITY: "opacity",
  2354. };
  2355. const REGEXP_ANY_SYMBOL = ".*";
  2356. const REGEXP_WITH_FLAGS_REGEXP = /^\s*\/.*\/[gmisuy]*\s*$/;
  2357. const removeContentQuotes = (str) => {
  2358. return str.replace(/^(["'])([\s\S]*)\1$/, "$2");
  2359. };
  2360. const addUrlPropertyQuotes = (str) => {
  2361. if (!str.includes('url("')) {
  2362. const re = /url\((.*?)\)/g;
  2363. return str.replace(re, 'url("$1")');
  2364. }
  2365. return str;
  2366. };
  2367. const addUrlQuotesTo = {
  2368. regexpArg: (str) => {
  2369. const re = /(\^)?url(\\)?\\\((\w|\[\w)/g;
  2370. return str.replace(re, '$1url$2\\(\\"?$3');
  2371. },
  2372. noneRegexpArg: addUrlPropertyQuotes,
  2373. };
  2374. const escapeRegExp = (str) => {
  2375. const specials = [
  2376. ".",
  2377. "+",
  2378. "?",
  2379. "$",
  2380. "{",
  2381. "}",
  2382. "(",
  2383. ")",
  2384. "[",
  2385. "]",
  2386. "\\",
  2387. "/",
  2388. ];
  2389. const specialsRegex = new RegExp(`[${specials.join("\\")}]`, "g");
  2390. return str.replace(specialsRegex, "\\$&");
  2391. };
  2392. const convertStyleMatchValueToRegexp = (rawValue) => {
  2393. let value;
  2394. if (rawValue.startsWith(SLASH) && rawValue.endsWith(SLASH)) {
  2395. value = addUrlQuotesTo.regexpArg(rawValue);
  2396. value = value.slice(1, -1);
  2397. } else {
  2398. value = addUrlQuotesTo.noneRegexpArg(rawValue);
  2399. value = value.replace(/\\([\\()[\]"])/g, "$1");
  2400. value = escapeRegExp(value);
  2401. value = replaceAll(value, ASTERISK, REGEXP_ANY_SYMBOL);
  2402. }
  2403. return new RegExp(value, "i");
  2404. };
  2405. const normalizePropertyValue = (propertyName, propertyValue) => {
  2406. let normalized = "";
  2407. switch (propertyName) {
  2408. case CSS_PROPERTY.BACKGROUND:
  2409. case CSS_PROPERTY.BACKGROUND_IMAGE:
  2410. normalized = addUrlPropertyQuotes(propertyValue);
  2411. break;
  2412. case CSS_PROPERTY.CONTENT:
  2413. normalized = removeContentQuotes(propertyValue);
  2414. break;
  2415. case CSS_PROPERTY.OPACITY:
  2416. normalized = isSafariBrowser
  2417. ? (Math.round(parseFloat(propertyValue) * 100) / 100).toString()
  2418. : propertyValue;
  2419. break;
  2420. default:
  2421. normalized = propertyValue;
  2422. }
  2423. return normalized;
  2424. };
  2425. const getComputedStylePropertyValue = (
  2426. domElement,
  2427. propertyName,
  2428. regularPseudoElement
  2429. ) => {
  2430. const style = window.getComputedStyle(domElement, regularPseudoElement);
  2431. const propertyValue = style.getPropertyValue(propertyName);
  2432. return normalizePropertyValue(propertyName, propertyValue);
  2433. };
  2434. const getPseudoArgData = (pseudoArg, separator) => {
  2435. const index = pseudoArg.indexOf(separator);
  2436. let name;
  2437. let value;
  2438. if (index > -1) {
  2439. name = pseudoArg.substring(0, index).trim();
  2440. value = pseudoArg.substring(index + 1).trim();
  2441. } else {
  2442. name = pseudoArg;
  2443. }
  2444. return {
  2445. name,
  2446. value,
  2447. };
  2448. };
  2449. const parseStyleMatchArg = (pseudoName, rawArg) => {
  2450. const { name, value } = getPseudoArgData(rawArg, COMMA);
  2451. let regularPseudoElement = name;
  2452. let styleMatchArg = value;
  2453. if (!Object.values(REGULAR_PSEUDO_ELEMENTS).includes(name)) {
  2454. regularPseudoElement = null;
  2455. styleMatchArg = rawArg;
  2456. }
  2457. if (!styleMatchArg) {
  2458. throw new Error(
  2459. `Required style property argument part is missing in :${pseudoName}() arg: '${rawArg}'`
  2460. );
  2461. }
  2462. if (regularPseudoElement) {
  2463. regularPseudoElement = `${COLON}${COLON}${regularPseudoElement}`;
  2464. }
  2465. return {
  2466. regularPseudoElement,
  2467. styleMatchArg,
  2468. };
  2469. };
  2470. const isStyleMatched = (argsData) => {
  2471. const { pseudoName, pseudoArg, domElement } = argsData;
  2472. const { regularPseudoElement, styleMatchArg } = parseStyleMatchArg(
  2473. pseudoName,
  2474. pseudoArg
  2475. );
  2476. const { name: matchName, value: matchValue } = getPseudoArgData(
  2477. styleMatchArg,
  2478. COLON
  2479. );
  2480. if (!matchName || !matchValue) {
  2481. throw new Error(
  2482. `Required property name or value is missing in :${pseudoName}() arg: '${styleMatchArg}'`
  2483. );
  2484. }
  2485. let valueRegexp;
  2486. try {
  2487. valueRegexp = convertStyleMatchValueToRegexp(matchValue);
  2488. } catch (e) {
  2489. logger.error(getErrorMessage(e));
  2490. throw new Error(
  2491. `Invalid argument of :${pseudoName}() pseudo-class: '${styleMatchArg}'`
  2492. );
  2493. }
  2494. const value = getComputedStylePropertyValue(
  2495. domElement,
  2496. matchName,
  2497. regularPseudoElement
  2498. );
  2499. return valueRegexp && valueRegexp.test(value);
  2500. };
  2501. const validateStrMatcherArg = (arg) => {
  2502. if (arg.includes(SLASH)) {
  2503. return false;
  2504. }
  2505. if (!/^[\w-]+$/.test(arg)) {
  2506. return false;
  2507. }
  2508. return true;
  2509. };
  2510. const getValidMatcherArg = function (rawArg) {
  2511. let isWildcardAllowed =
  2512. arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  2513. let arg;
  2514. if (
  2515. rawArg.length > 1 &&
  2516. rawArg.startsWith(DOUBLE_QUOTE) &&
  2517. rawArg.endsWith(DOUBLE_QUOTE)
  2518. ) {
  2519. rawArg = rawArg.slice(1, -1);
  2520. }
  2521. if (rawArg === "") {
  2522. throw new Error("Argument should be specified. Empty arg is invalid.");
  2523. }
  2524. if (rawArg.startsWith(SLASH) && rawArg.endsWith(SLASH)) {
  2525. if (rawArg.length > 2) {
  2526. arg = toRegExp(rawArg);
  2527. } else {
  2528. throw new Error(`Invalid regexp: '${rawArg}'`);
  2529. }
  2530. } else if (rawArg.includes(ASTERISK)) {
  2531. if (rawArg === ASTERISK && !isWildcardAllowed) {
  2532. throw new Error(`Argument should be more specific than ${rawArg}`);
  2533. }
  2534. arg = replaceAll(rawArg, ASTERISK, REGEXP_ANY_SYMBOL);
  2535. arg = new RegExp(arg);
  2536. } else {
  2537. if (!validateStrMatcherArg(rawArg)) {
  2538. throw new Error(`Invalid argument: '${rawArg}'`);
  2539. }
  2540. arg = rawArg;
  2541. }
  2542. return arg;
  2543. };
  2544. const getRawMatchingData = (pseudoName, pseudoArg) => {
  2545. const { name: rawName, value: rawValue } = getPseudoArgData(
  2546. pseudoArg,
  2547. EQUAL_SIGN
  2548. );
  2549. if (!rawName) {
  2550. throw new Error(
  2551. `Required attribute name is missing in :${pseudoName} arg: ${pseudoArg}`
  2552. );
  2553. }
  2554. return {
  2555. rawName,
  2556. rawValue,
  2557. };
  2558. };
  2559. const isAttributeMatched = (argsData) => {
  2560. const { pseudoName, pseudoArg, domElement } = argsData;
  2561. const elementAttributes = domElement.attributes;
  2562. if (elementAttributes.length === 0) {
  2563. return false;
  2564. }
  2565. const { rawName: rawAttrName, rawValue: rawAttrValue } = getRawMatchingData(
  2566. pseudoName,
  2567. pseudoArg
  2568. );
  2569. let attrNameMatch;
  2570. try {
  2571. attrNameMatch = getValidMatcherArg(rawAttrName);
  2572. } catch (e) {
  2573. const errorMessage = getErrorMessage(e);
  2574. logger.error(errorMessage);
  2575. throw new SyntaxError(errorMessage);
  2576. }
  2577. let isMatched = false;
  2578. let i = 0;
  2579. while (i < elementAttributes.length && !isMatched) {
  2580. const attr = elementAttributes[i];
  2581. if (!attr) {
  2582. break;
  2583. }
  2584. const isNameMatched =
  2585. attrNameMatch instanceof RegExp
  2586. ? attrNameMatch.test(attr.name)
  2587. : attrNameMatch === attr.name;
  2588. if (!rawAttrValue) {
  2589. isMatched = isNameMatched;
  2590. } else {
  2591. let attrValueMatch;
  2592. try {
  2593. attrValueMatch = getValidMatcherArg(rawAttrValue);
  2594. } catch (e) {
  2595. const errorMessage = getErrorMessage(e);
  2596. logger.error(errorMessage);
  2597. throw new SyntaxError(errorMessage);
  2598. }
  2599. const isValueMatched =
  2600. attrValueMatch instanceof RegExp
  2601. ? attrValueMatch.test(attr.value)
  2602. : attrValueMatch === attr.value;
  2603. isMatched = isNameMatched && isValueMatched;
  2604. }
  2605. i += 1;
  2606. }
  2607. return isMatched;
  2608. };
  2609. const parseRawPropChain = (input) => {
  2610. if (
  2611. input.length > 1 &&
  2612. input.startsWith(DOUBLE_QUOTE) &&
  2613. input.endsWith(DOUBLE_QUOTE)
  2614. ) {
  2615. input = input.slice(1, -1);
  2616. }
  2617. const chainChunks = input.split(DOT);
  2618. const chainPatterns = [];
  2619. let patternBuffer = "";
  2620. let isRegexpPattern = false;
  2621. let i = 0;
  2622. while (i < chainChunks.length) {
  2623. const chunk = getItemByIndex(
  2624. chainChunks,
  2625. i,
  2626. `Invalid pseudo-class arg: '${input}'`
  2627. );
  2628. if (
  2629. chunk.startsWith(SLASH) &&
  2630. chunk.endsWith(SLASH) &&
  2631. chunk.length > 2
  2632. ) {
  2633. chainPatterns.push(chunk);
  2634. } else if (chunk.startsWith(SLASH)) {
  2635. isRegexpPattern = true;
  2636. patternBuffer += chunk;
  2637. } else if (chunk.endsWith(SLASH)) {
  2638. isRegexpPattern = false;
  2639. patternBuffer += `.${chunk}`;
  2640. chainPatterns.push(patternBuffer);
  2641. patternBuffer = "";
  2642. } else {
  2643. if (isRegexpPattern) {
  2644. patternBuffer += chunk;
  2645. } else {
  2646. chainPatterns.push(chunk);
  2647. }
  2648. }
  2649. i += 1;
  2650. }
  2651. if (patternBuffer.length > 0) {
  2652. throw new Error(`Invalid regexp property pattern '${input}'`);
  2653. }
  2654. const chainMatchPatterns = chainPatterns.map((pattern) => {
  2655. if (pattern.length === 0) {
  2656. throw new Error(
  2657. `Empty pattern '${pattern}' is invalid in chain '${input}'`
  2658. );
  2659. }
  2660. let validPattern;
  2661. try {
  2662. validPattern = getValidMatcherArg(pattern, true);
  2663. } catch (e) {
  2664. logger.error(getErrorMessage(e));
  2665. throw new Error(
  2666. `Invalid property pattern '${pattern}' in property chain '${input}'`
  2667. );
  2668. }
  2669. return validPattern;
  2670. });
  2671. return chainMatchPatterns;
  2672. };
  2673. const filterRootsByRegexpChain = function (base, chain) {
  2674. let output =
  2675. arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
  2676. const tempProp = getFirst(chain);
  2677. if (chain.length === 1) {
  2678. let key;
  2679. for (key in base) {
  2680. if (tempProp instanceof RegExp) {
  2681. if (tempProp.test(key)) {
  2682. output.push({
  2683. base,
  2684. prop: key,
  2685. value: base[key],
  2686. });
  2687. }
  2688. } else if (tempProp === key) {
  2689. output.push({
  2690. base,
  2691. prop: tempProp,
  2692. value: base[key],
  2693. });
  2694. }
  2695. }
  2696. return output;
  2697. }
  2698. if (tempProp instanceof RegExp) {
  2699. const nextProp = chain.slice(1);
  2700. const baseKeys = [];
  2701. for (const key in base) {
  2702. if (tempProp.test(key)) {
  2703. baseKeys.push(key);
  2704. }
  2705. }
  2706. baseKeys.forEach((key) => {
  2707. var _Object$getOwnPropert;
  2708. const item =
  2709. (_Object$getOwnPropert = Object.getOwnPropertyDescriptor(
  2710. base,
  2711. key
  2712. )) === null || _Object$getOwnPropert === void 0
  2713. ? void 0
  2714. : _Object$getOwnPropert.value;
  2715. filterRootsByRegexpChain(item, nextProp, output);
  2716. });
  2717. }
  2718. if (base && typeof tempProp === "string") {
  2719. var _Object$getOwnPropert2;
  2720. const nextBase =
  2721. (_Object$getOwnPropert2 = Object.getOwnPropertyDescriptor(
  2722. base,
  2723. tempProp
  2724. )) === null || _Object$getOwnPropert2 === void 0
  2725. ? void 0
  2726. : _Object$getOwnPropert2.value;
  2727. chain = chain.slice(1);
  2728. if (nextBase !== undefined) {
  2729. filterRootsByRegexpChain(nextBase, chain, output);
  2730. }
  2731. }
  2732. return output;
  2733. };
  2734. const isPropertyMatched = (argsData) => {
  2735. const { pseudoName, pseudoArg, domElement } = argsData;
  2736. const { rawName: rawPropertyName, rawValue: rawPropertyValue } =
  2737. getRawMatchingData(pseudoName, pseudoArg);
  2738. if (rawPropertyName.includes("\\/") || rawPropertyName.includes("\\.")) {
  2739. throw new Error(
  2740. `Invalid :${pseudoName} name pattern: ${rawPropertyName}`
  2741. );
  2742. }
  2743. let propChainMatches;
  2744. try {
  2745. propChainMatches = parseRawPropChain(rawPropertyName);
  2746. } catch (e) {
  2747. const errorMessage = getErrorMessage(e);
  2748. logger.error(errorMessage);
  2749. throw new SyntaxError(errorMessage);
  2750. }
  2751. const ownerObjArr = filterRootsByRegexpChain(domElement, propChainMatches);
  2752. if (ownerObjArr.length === 0) {
  2753. return false;
  2754. }
  2755. let isMatched = true;
  2756. if (rawPropertyValue) {
  2757. let propValueMatch;
  2758. try {
  2759. propValueMatch = getValidMatcherArg(rawPropertyValue);
  2760. } catch (e) {
  2761. const errorMessage = getErrorMessage(e);
  2762. logger.error(errorMessage);
  2763. throw new SyntaxError(errorMessage);
  2764. }
  2765. if (propValueMatch) {
  2766. for (let i = 0; i < ownerObjArr.length; i += 1) {
  2767. var _ownerObjArr$i;
  2768. const realValue =
  2769. (_ownerObjArr$i = ownerObjArr[i]) === null ||
  2770. _ownerObjArr$i === void 0
  2771. ? void 0
  2772. : _ownerObjArr$i.value;
  2773. if (propValueMatch instanceof RegExp) {
  2774. isMatched = propValueMatch.test(convertTypeIntoString(realValue));
  2775. } else {
  2776. if (realValue === "null" || realValue === "undefined") {
  2777. isMatched = propValueMatch === realValue;
  2778. break;
  2779. }
  2780. isMatched = convertTypeFromString(propValueMatch) === realValue;
  2781. }
  2782. if (isMatched) {
  2783. break;
  2784. }
  2785. }
  2786. }
  2787. }
  2788. return isMatched;
  2789. };
  2790. const isTextMatched = (argsData) => {
  2791. const { pseudoName, pseudoArg, domElement } = argsData;
  2792. const textContent = getNodeTextContent(domElement);
  2793. let isTextContentMatched;
  2794. let pseudoArgToMatch = pseudoArg;
  2795. if (
  2796. pseudoArgToMatch.startsWith(SLASH) &&
  2797. REGEXP_WITH_FLAGS_REGEXP.test(pseudoArgToMatch)
  2798. ) {
  2799. const flagsIndex = pseudoArgToMatch.lastIndexOf("/");
  2800. const flagsStr = pseudoArgToMatch.substring(flagsIndex + 1);
  2801. pseudoArgToMatch = pseudoArgToMatch
  2802. .substring(0, flagsIndex + 1)
  2803. .slice(1, -1)
  2804. .replace(/\\([\\"])/g, "$1");
  2805. let regex;
  2806. try {
  2807. regex = new RegExp(pseudoArgToMatch, flagsStr);
  2808. } catch (e) {
  2809. throw new Error(
  2810. `Invalid argument of :${pseudoName}() pseudo-class: ${pseudoArg}`
  2811. );
  2812. }
  2813. isTextContentMatched = regex.test(textContent);
  2814. } else {
  2815. pseudoArgToMatch = pseudoArgToMatch.replace(/\\([\\()[\]"])/g, "$1");
  2816. isTextContentMatched = textContent.includes(pseudoArgToMatch);
  2817. }
  2818. return isTextContentMatched;
  2819. };
  2820. const getValidNumberAncestorArg = (rawArg, pseudoName) => {
  2821. const deep = Number(rawArg);
  2822. if (Number.isNaN(deep) || deep < 1 || deep >= 256) {
  2823. throw new Error(
  2824. `Invalid argument of :${pseudoName} pseudo-class: '${rawArg}'`
  2825. );
  2826. }
  2827. return deep;
  2828. };
  2829. const getNthAncestor = (domElement, nth, pseudoName) => {
  2830. let ancestor = null;
  2831. let i = 0;
  2832. while (i < nth) {
  2833. ancestor = domElement.parentElement;
  2834. if (!ancestor) {
  2835. throw new Error(
  2836. `Out of DOM: Argument of :${pseudoName}() pseudo-class is too big '${nth}'.`
  2837. );
  2838. }
  2839. domElement = ancestor;
  2840. i += 1;
  2841. }
  2842. return ancestor;
  2843. };
  2844. const validateStandardSelector = (selector) => {
  2845. let isValid;
  2846. try {
  2847. document.querySelectorAll(selector);
  2848. isValid = true;
  2849. } catch (e) {
  2850. isValid = false;
  2851. }
  2852. return isValid;
  2853. };
  2854. const matcherWrapper = (callback, argsData, errorMessage) => {
  2855. let isMatched;
  2856. try {
  2857. isMatched = callback(argsData);
  2858. } catch (e) {
  2859. logger.error(getErrorMessage(e));
  2860. throw new Error(errorMessage);
  2861. }
  2862. return isMatched;
  2863. };
  2864. const getAbsolutePseudoError = (propDesc, pseudoName, pseudoArg) => {
  2865. return `${MATCHING_ELEMENT_ERROR_PREFIX} ${propDesc}, may be invalid :${pseudoName}() pseudo-class arg: '${pseudoArg}'`;
  2866. };
  2867. const isMatchedByAbsolutePseudo = (domElement, pseudoName, pseudoArg) => {
  2868. let argsData;
  2869. let errorMessage;
  2870. let callback;
  2871. switch (pseudoName) {
  2872. case CONTAINS_PSEUDO:
  2873. case HAS_TEXT_PSEUDO:
  2874. case ABP_CONTAINS_PSEUDO:
  2875. callback = isTextMatched;
  2876. argsData = {
  2877. pseudoName,
  2878. pseudoArg,
  2879. domElement,
  2880. };
  2881. errorMessage = getAbsolutePseudoError(
  2882. "text content",
  2883. pseudoName,
  2884. pseudoArg
  2885. );
  2886. break;
  2887. case MATCHES_CSS_PSEUDO:
  2888. case MATCHES_CSS_AFTER_PSEUDO:
  2889. case MATCHES_CSS_BEFORE_PSEUDO:
  2890. callback = isStyleMatched;
  2891. argsData = {
  2892. pseudoName,
  2893. pseudoArg,
  2894. domElement,
  2895. };
  2896. errorMessage = getAbsolutePseudoError("style", pseudoName, pseudoArg);
  2897. break;
  2898. case MATCHES_ATTR_PSEUDO_CLASS_MARKER:
  2899. callback = isAttributeMatched;
  2900. argsData = {
  2901. domElement,
  2902. pseudoName,
  2903. pseudoArg,
  2904. };
  2905. errorMessage = getAbsolutePseudoError(
  2906. "attributes",
  2907. pseudoName,
  2908. pseudoArg
  2909. );
  2910. break;
  2911. case MATCHES_PROPERTY_PSEUDO_CLASS_MARKER:
  2912. callback = isPropertyMatched;
  2913. argsData = {
  2914. domElement,
  2915. pseudoName,
  2916. pseudoArg,
  2917. };
  2918. errorMessage = getAbsolutePseudoError(
  2919. "properties",
  2920. pseudoName,
  2921. pseudoArg
  2922. );
  2923. break;
  2924. default:
  2925. throw new Error(`Unknown absolute pseudo-class :${pseudoName}()`);
  2926. }
  2927. return matcherWrapper(callback, argsData, errorMessage);
  2928. };
  2929. const findByAbsolutePseudoPseudo = {
  2930. nthAncestor: (domElements, rawPseudoArg, pseudoName) => {
  2931. const deep = getValidNumberAncestorArg(rawPseudoArg, pseudoName);
  2932. const ancestors = domElements
  2933. .map((domElement) => {
  2934. let ancestor = null;
  2935. try {
  2936. ancestor = getNthAncestor(domElement, deep, pseudoName);
  2937. } catch (e) {
  2938. logger.error(getErrorMessage(e));
  2939. }
  2940. return ancestor;
  2941. })
  2942. .filter(isHtmlElement);
  2943. return ancestors;
  2944. },
  2945. xpath: (domElements, rawPseudoArg) => {
  2946. const foundElements = domElements.map((domElement) => {
  2947. const result = [];
  2948. let xpathResult;
  2949. try {
  2950. xpathResult = document.evaluate(
  2951. rawPseudoArg,
  2952. domElement,
  2953. null,
  2954. window.XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
  2955. null
  2956. );
  2957. } catch (e) {
  2958. logger.error(getErrorMessage(e));
  2959. throw new Error(
  2960. `Invalid argument of :xpath() pseudo-class: '${rawPseudoArg}'`
  2961. );
  2962. }
  2963. let node = xpathResult.iterateNext();
  2964. while (node) {
  2965. if (isHtmlElement(node)) {
  2966. result.push(node);
  2967. }
  2968. node = xpathResult.iterateNext();
  2969. }
  2970. return result;
  2971. });
  2972. return flatten(foundElements);
  2973. },
  2974. upward: (domElements, rawPseudoArg) => {
  2975. if (!validateStandardSelector(rawPseudoArg)) {
  2976. throw new Error(
  2977. `Invalid argument of :upward pseudo-class: '${rawPseudoArg}'`
  2978. );
  2979. }
  2980. const closestAncestors = domElements
  2981. .map((domElement) => {
  2982. const parent = domElement.parentElement;
  2983. if (!parent) {
  2984. return null;
  2985. }
  2986. return parent.closest(rawPseudoArg);
  2987. })
  2988. .filter(isHtmlElement);
  2989. return closestAncestors;
  2990. },
  2991. };
  2992. const scopeDirectChildren = `${SCOPE_CSS_PSEUDO_CLASS}${CHILD_COMBINATOR}`;
  2993. const scopeAnyChildren = `${SCOPE_CSS_PSEUDO_CLASS}${DESCENDANT_COMBINATOR}`;
  2994. const getFirstInnerRegularChild = (selectorNode, pseudoName) => {
  2995. return getFirstRegularChild(
  2996. selectorNode.children,
  2997. `RegularSelector is missing for :${pseudoName}() pseudo-class`
  2998. );
  2999. };
  3000. const hasRelativesBySelectorList = (argsData) => {
  3001. const { element, relativeSelectorList, pseudoName } = argsData;
  3002. return relativeSelectorList.children.every((selectorNode) => {
  3003. const relativeRegularSelector = getFirstInnerRegularChild(
  3004. selectorNode,
  3005. pseudoName
  3006. );
  3007. let specifiedSelector = "";
  3008. let rootElement = null;
  3009. const regularSelector = getNodeValue(relativeRegularSelector);
  3010. if (
  3011. regularSelector.startsWith(NEXT_SIBLING_COMBINATOR) ||
  3012. regularSelector.startsWith(SUBSEQUENT_SIBLING_COMBINATOR)
  3013. ) {
  3014. rootElement = element.parentElement;
  3015. const elementSelectorText = getElementSelectorDesc(element);
  3016. specifiedSelector = `${scopeDirectChildren}${elementSelectorText}${regularSelector}`;
  3017. } else if (regularSelector === ASTERISK) {
  3018. rootElement = element;
  3019. specifiedSelector = `${scopeAnyChildren}${ASTERISK}`;
  3020. } else {
  3021. specifiedSelector = `${scopeAnyChildren}${regularSelector}`;
  3022. rootElement = element;
  3023. }
  3024. if (!rootElement) {
  3025. throw new Error(
  3026. `Selection by :${pseudoName}() pseudo-class is not possible`
  3027. );
  3028. }
  3029. let relativeElements;
  3030. try {
  3031. relativeElements = getElementsForSelectorNode(
  3032. selectorNode,
  3033. rootElement,
  3034. specifiedSelector
  3035. );
  3036. } catch (e) {
  3037. logger.error(getErrorMessage(e));
  3038. throw new Error(
  3039. `Invalid selector for :${pseudoName}() pseudo-class: '${regularSelector}'`
  3040. );
  3041. }
  3042. return relativeElements.length > 0;
  3043. });
  3044. };
  3045. const isAnyElementBySelectorList = (argsData) => {
  3046. const { element, relativeSelectorList, pseudoName } = argsData;
  3047. return relativeSelectorList.children.some((selectorNode) => {
  3048. const relativeRegularSelector = getFirstInnerRegularChild(
  3049. selectorNode,
  3050. pseudoName
  3051. );
  3052. const rootElement = getParent(
  3053. element,
  3054. `Selection by :${pseudoName}() pseudo-class is not possible`
  3055. );
  3056. const specifiedSelector = `${scopeDirectChildren}${getNodeValue(
  3057. relativeRegularSelector
  3058. )}`;
  3059. let anyElements;
  3060. try {
  3061. anyElements = getElementsForSelectorNode(
  3062. selectorNode,
  3063. rootElement,
  3064. specifiedSelector
  3065. );
  3066. } catch (e) {
  3067. return false;
  3068. }
  3069. return anyElements.includes(element);
  3070. });
  3071. };
  3072. const notElementBySelectorList = (argsData) => {
  3073. const { element, relativeSelectorList, pseudoName } = argsData;
  3074. return relativeSelectorList.children.every((selectorNode) => {
  3075. const relativeRegularSelector = getFirstInnerRegularChild(
  3076. selectorNode,
  3077. pseudoName
  3078. );
  3079. const rootElement = getParent(
  3080. element,
  3081. `Selection by :${pseudoName}() pseudo-class is not possible`
  3082. );
  3083. const specifiedSelector = `${scopeDirectChildren}${getNodeValue(
  3084. relativeRegularSelector
  3085. )}`;
  3086. let anyElements;
  3087. try {
  3088. anyElements = getElementsForSelectorNode(
  3089. selectorNode,
  3090. rootElement,
  3091. specifiedSelector
  3092. );
  3093. } catch (e) {
  3094. logger.error(getErrorMessage(e));
  3095. throw new Error(
  3096. `Invalid selector for :${pseudoName}() pseudo-class: '${getNodeValue(
  3097. relativeRegularSelector
  3098. )}'`
  3099. );
  3100. }
  3101. return !anyElements.includes(element);
  3102. });
  3103. };
  3104. const getByRegularSelector = (
  3105. regularSelectorNode,
  3106. root,
  3107. specifiedSelector
  3108. ) => {
  3109. const selectorText = specifiedSelector
  3110. ? specifiedSelector
  3111. : getNodeValue(regularSelectorNode);
  3112. let selectedElements = [];
  3113. try {
  3114. selectedElements = Array.from(root.querySelectorAll(selectorText));
  3115. } catch (e) {
  3116. throw new Error(
  3117. `Error: unable to select by '${selectorText}' ${getErrorMessage(e)}`
  3118. );
  3119. }
  3120. return selectedElements;
  3121. };
  3122. const getByExtendedSelector = (domElements, extendedSelectorNode) => {
  3123. let foundElements = [];
  3124. const extendedPseudoClassNode = getPseudoClassNode(extendedSelectorNode);
  3125. const pseudoName = getNodeName(extendedPseudoClassNode);
  3126. if (isAbsolutePseudoClass(pseudoName)) {
  3127. const absolutePseudoArg = getNodeValue(
  3128. extendedPseudoClassNode,
  3129. `Missing arg for :${pseudoName}() pseudo-class`
  3130. );
  3131. if (pseudoName === NTH_ANCESTOR_PSEUDO_CLASS_MARKER) {
  3132. foundElements = findByAbsolutePseudoPseudo.nthAncestor(
  3133. domElements,
  3134. absolutePseudoArg,
  3135. pseudoName
  3136. );
  3137. } else if (pseudoName === XPATH_PSEUDO_CLASS_MARKER) {
  3138. try {
  3139. document.createExpression(absolutePseudoArg, null);
  3140. } catch (e) {
  3141. throw new Error(
  3142. `Invalid argument of :${pseudoName}() pseudo-class: '${absolutePseudoArg}'`
  3143. );
  3144. }
  3145. foundElements = findByAbsolutePseudoPseudo.xpath(
  3146. domElements,
  3147. absolutePseudoArg
  3148. );
  3149. } else if (pseudoName === UPWARD_PSEUDO_CLASS_MARKER) {
  3150. if (Number.isNaN(Number(absolutePseudoArg))) {
  3151. foundElements = findByAbsolutePseudoPseudo.upward(
  3152. domElements,
  3153. absolutePseudoArg
  3154. );
  3155. } else {
  3156. foundElements = findByAbsolutePseudoPseudo.nthAncestor(
  3157. domElements,
  3158. absolutePseudoArg,
  3159. pseudoName
  3160. );
  3161. }
  3162. } else {
  3163. foundElements = domElements.filter((element) => {
  3164. return isMatchedByAbsolutePseudo(
  3165. element,
  3166. pseudoName,
  3167. absolutePseudoArg
  3168. );
  3169. });
  3170. }
  3171. } else if (isRelativePseudoClass(pseudoName)) {
  3172. const relativeSelectorList = getRelativeSelectorListNode(
  3173. extendedPseudoClassNode
  3174. );
  3175. let relativePredicate;
  3176. switch (pseudoName) {
  3177. case HAS_PSEUDO_CLASS_MARKER:
  3178. case ABP_HAS_PSEUDO_CLASS_MARKER:
  3179. relativePredicate = (element) =>
  3180. hasRelativesBySelectorList({
  3181. element,
  3182. relativeSelectorList,
  3183. pseudoName,
  3184. });
  3185. break;
  3186. case IS_PSEUDO_CLASS_MARKER:
  3187. relativePredicate = (element) =>
  3188. isAnyElementBySelectorList({
  3189. element,
  3190. relativeSelectorList,
  3191. pseudoName,
  3192. });
  3193. break;
  3194. case NOT_PSEUDO_CLASS_MARKER:
  3195. relativePredicate = (element) =>
  3196. notElementBySelectorList({
  3197. element,
  3198. relativeSelectorList,
  3199. pseudoName,
  3200. });
  3201. break;
  3202. default:
  3203. throw new Error(`Unknown relative pseudo-class: '${pseudoName}'`);
  3204. }
  3205. foundElements = domElements.filter(relativePredicate);
  3206. } else {
  3207. throw new Error(`Unknown extended pseudo-class: '${pseudoName}'`);
  3208. }
  3209. return foundElements;
  3210. };
  3211. const getByFollowingRegularSelector = (domElements, regularSelectorNode) => {
  3212. let foundElements = [];
  3213. const value = getNodeValue(regularSelectorNode);
  3214. if (value.startsWith(CHILD_COMBINATOR)) {
  3215. foundElements = domElements.map((root) => {
  3216. const specifiedSelector = `${SCOPE_CSS_PSEUDO_CLASS}${value}`;
  3217. return getByRegularSelector(
  3218. regularSelectorNode,
  3219. root,
  3220. specifiedSelector
  3221. );
  3222. });
  3223. } else if (
  3224. value.startsWith(NEXT_SIBLING_COMBINATOR) ||
  3225. value.startsWith(SUBSEQUENT_SIBLING_COMBINATOR)
  3226. ) {
  3227. foundElements = domElements.map((element) => {
  3228. const rootElement = element.parentElement;
  3229. if (!rootElement) {
  3230. return [];
  3231. }
  3232. const elementSelectorText = getElementSelectorDesc(element);
  3233. const specifiedSelector = `${scopeDirectChildren}${elementSelectorText}${value}`;
  3234. const selected = getByRegularSelector(
  3235. regularSelectorNode,
  3236. rootElement,
  3237. specifiedSelector
  3238. );
  3239. return selected;
  3240. });
  3241. } else {
  3242. foundElements = domElements.map((root) => {
  3243. const specifiedSelector = `${scopeAnyChildren}${getNodeValue(
  3244. regularSelectorNode
  3245. )}`;
  3246. return getByRegularSelector(
  3247. regularSelectorNode,
  3248. root,
  3249. specifiedSelector
  3250. );
  3251. });
  3252. }
  3253. return flatten(foundElements);
  3254. };
  3255. const getElementsForSelectorNode = (
  3256. selectorNode,
  3257. root,
  3258. specifiedSelector
  3259. ) => {
  3260. let selectedElements = [];
  3261. let i = 0;
  3262. while (i < selectorNode.children.length) {
  3263. const selectorNodeChild = getItemByIndex(
  3264. selectorNode.children,
  3265. i,
  3266. "selectorNodeChild should be specified"
  3267. );
  3268. if (i === 0) {
  3269. selectedElements = getByRegularSelector(
  3270. selectorNodeChild,
  3271. root,
  3272. specifiedSelector
  3273. );
  3274. } else if (isExtendedSelectorNode(selectorNodeChild)) {
  3275. selectedElements = getByExtendedSelector(
  3276. selectedElements,
  3277. selectorNodeChild
  3278. );
  3279. } else if (isRegularSelectorNode(selectorNodeChild)) {
  3280. selectedElements = getByFollowingRegularSelector(
  3281. selectedElements,
  3282. selectorNodeChild
  3283. );
  3284. }
  3285. i += 1;
  3286. }
  3287. return selectedElements;
  3288. };
  3289. const selectElementsByAst = function (ast) {
  3290. let doc =
  3291. arguments.length > 1 && arguments[1] !== undefined
  3292. ? arguments[1]
  3293. : document;
  3294. const selectedElements = [];
  3295. ast.children.forEach((selectorNode) => {
  3296. selectedElements.push(...getElementsForSelectorNode(selectorNode, doc));
  3297. });
  3298. const uniqueElements = [...new Set(flatten(selectedElements))];
  3299. return uniqueElements;
  3300. };
  3301. class ExtCssDocument {
  3302. constructor() {
  3303. this.astCache = new Map();
  3304. }
  3305. saveAstToCache(selector, ast) {
  3306. this.astCache.set(selector, ast);
  3307. }
  3308. getAstFromCache(selector) {
  3309. const cachedAst = this.astCache.get(selector) || null;
  3310. return cachedAst;
  3311. }
  3312. getSelectorAst(selector) {
  3313. let ast = this.getAstFromCache(selector);
  3314. if (!ast) {
  3315. ast = parse(selector);
  3316. }
  3317. this.saveAstToCache(selector, ast);
  3318. return ast;
  3319. }
  3320. querySelectorAll(selector) {
  3321. const ast = this.getSelectorAst(selector);
  3322. return selectElementsByAst(ast);
  3323. }
  3324. }
  3325. const extCssDocument = new ExtCssDocument();
  3326. const getObjectFromEntries = (entries) => {
  3327. const object = {};
  3328. entries.forEach((el) => {
  3329. const [key, value] = el;
  3330. object[key] = value;
  3331. });
  3332. return object;
  3333. };
  3334. const DEBUG_PSEUDO_PROPERTY_KEY = "debug";
  3335. const parseRemoveSelector = (rawSelector) => {
  3336. const VALID_REMOVE_MARKER = `${COLON}${REMOVE_PSEUDO_MARKER}${BRACKET.PARENTHESES.LEFT}${BRACKET.PARENTHESES.RIGHT}`;
  3337. const INVALID_REMOVE_MARKER = `${COLON}${REMOVE_PSEUDO_MARKER}${BRACKET.PARENTHESES.LEFT}`;
  3338. let selector;
  3339. let shouldRemove = false;
  3340. const firstIndex = rawSelector.indexOf(VALID_REMOVE_MARKER);
  3341. if (firstIndex === 0) {
  3342. throw new Error(
  3343. `${REMOVE_ERROR_PREFIX.NO_TARGET_SELECTOR}: '${rawSelector}'`
  3344. );
  3345. } else if (firstIndex > 0) {
  3346. if (firstIndex !== rawSelector.lastIndexOf(VALID_REMOVE_MARKER)) {
  3347. throw new Error(
  3348. `${REMOVE_ERROR_PREFIX.MULTIPLE_USAGE}: '${rawSelector}'`
  3349. );
  3350. } else if (firstIndex + VALID_REMOVE_MARKER.length < rawSelector.length) {
  3351. throw new Error(
  3352. `${REMOVE_ERROR_PREFIX.INVALID_POSITION}: '${rawSelector}'`
  3353. );
  3354. } else {
  3355. selector = rawSelector.substring(0, firstIndex);
  3356. shouldRemove = true;
  3357. }
  3358. } else if (rawSelector.includes(INVALID_REMOVE_MARKER)) {
  3359. throw new Error(
  3360. `${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${rawSelector}'`
  3361. );
  3362. } else {
  3363. selector = rawSelector;
  3364. }
  3365. const stylesOfSelector = shouldRemove
  3366. ? [
  3367. {
  3368. property: REMOVE_PSEUDO_MARKER,
  3369. value: PSEUDO_PROPERTY_POSITIVE_VALUE,
  3370. },
  3371. ]
  3372. : [];
  3373. return {
  3374. selector,
  3375. stylesOfSelector,
  3376. };
  3377. };
  3378. const parseSelectorRulePart = (selectorBuffer, extCssDoc) => {
  3379. let selector = selectorBuffer.trim();
  3380. if (selector.startsWith(AT_RULE_MARKER)) {
  3381. throw new Error(`${NO_AT_RULE_ERROR_PREFIX}: '${selector}'.`);
  3382. }
  3383. let removeSelectorData;
  3384. try {
  3385. removeSelectorData = parseRemoveSelector(selector);
  3386. } catch (e) {
  3387. logger.error(getErrorMessage(e));
  3388. throw new Error(`${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${selector}'`);
  3389. }
  3390. let stylesOfSelector = [];
  3391. let success = false;
  3392. let ast;
  3393. try {
  3394. selector = removeSelectorData.selector;
  3395. stylesOfSelector = removeSelectorData.stylesOfSelector;
  3396. ast = extCssDoc.getSelectorAst(selector);
  3397. success = true;
  3398. } catch (e) {
  3399. success = false;
  3400. }
  3401. return {
  3402. success,
  3403. selector,
  3404. ast,
  3405. stylesOfSelector,
  3406. };
  3407. };
  3408. const createRawResultsMap = () => {
  3409. return new Map();
  3410. };
  3411. const saveToRawResults = (rawResults, rawRuleData) => {
  3412. const { selector, ast, rawStyles } = rawRuleData;
  3413. if (!rawStyles) {
  3414. throw new Error(`No style declaration for selector: '${selector}'`);
  3415. }
  3416. if (!ast) {
  3417. throw new Error(`No ast parsed for selector: '${selector}'`);
  3418. }
  3419. const storedRuleData = rawResults.get(selector);
  3420. if (!storedRuleData) {
  3421. rawResults.set(selector, {
  3422. ast,
  3423. styles: rawStyles,
  3424. });
  3425. } else {
  3426. storedRuleData.styles.push(...rawStyles);
  3427. }
  3428. };
  3429. const isRemoveSetInStyles = (styles) => {
  3430. return styles.some((s) => {
  3431. return (
  3432. s.property === REMOVE_PSEUDO_MARKER &&
  3433. s.value === PSEUDO_PROPERTY_POSITIVE_VALUE
  3434. );
  3435. });
  3436. };
  3437. const getDebugStyleValue = (styles) => {
  3438. const debugStyle = styles.find((s) => {
  3439. return s.property === DEBUG_PSEUDO_PROPERTY_KEY;
  3440. });
  3441. return debugStyle === null || debugStyle === void 0
  3442. ? void 0
  3443. : debugStyle.value;
  3444. };
  3445. const prepareRuleData = (rawRuleData) => {
  3446. const { selector, ast, rawStyles } = rawRuleData;
  3447. if (!ast) {
  3448. throw new Error(`AST should be parsed for selector: '${selector}'`);
  3449. }
  3450. if (!rawStyles) {
  3451. throw new Error(`Styles should be parsed for selector: '${selector}'`);
  3452. }
  3453. const ruleData = {
  3454. selector,
  3455. ast,
  3456. };
  3457. const debugValue = getDebugStyleValue(rawStyles);
  3458. const shouldRemove = isRemoveSetInStyles(rawStyles);
  3459. let styles = rawStyles;
  3460. if (debugValue) {
  3461. styles = rawStyles.filter(
  3462. (s) => s.property !== DEBUG_PSEUDO_PROPERTY_KEY
  3463. );
  3464. if (
  3465. debugValue === PSEUDO_PROPERTY_POSITIVE_VALUE ||
  3466. debugValue === DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE
  3467. ) {
  3468. ruleData.debug = debugValue;
  3469. }
  3470. }
  3471. if (shouldRemove) {
  3472. ruleData.style = {
  3473. [REMOVE_PSEUDO_MARKER]: PSEUDO_PROPERTY_POSITIVE_VALUE,
  3474. };
  3475. const contentStyle = styles.find(
  3476. (s) => s.property === CONTENT_CSS_PROPERTY
  3477. );
  3478. if (contentStyle) {
  3479. ruleData.style[CONTENT_CSS_PROPERTY] = contentStyle.value;
  3480. }
  3481. } else {
  3482. if (styles.length > 0) {
  3483. const stylesAsEntries = styles.map((style) => {
  3484. const { property, value } = style;
  3485. return [property, value];
  3486. });
  3487. const preparedStyleData = getObjectFromEntries(stylesAsEntries);
  3488. ruleData.style = preparedStyleData;
  3489. }
  3490. }
  3491. return ruleData;
  3492. };
  3493. const combineRulesData = (rawResults) => {
  3494. const results = [];
  3495. rawResults.forEach((value, key) => {
  3496. const selector = key;
  3497. const { ast, styles: rawStyles } = value;
  3498. results.push(
  3499. prepareRuleData({
  3500. selector,
  3501. ast,
  3502. rawStyles,
  3503. })
  3504. );
  3505. });
  3506. return results;
  3507. };
  3508. const tokenizeStyleBlock = (rawStyle) => {
  3509. const styleDeclaration = rawStyle.trim();
  3510. return tokenize(styleDeclaration, SUPPORTED_STYLE_DECLARATION_MARKS);
  3511. };
  3512. const DECLARATION_PART = {
  3513. PROPERTY: "property",
  3514. VALUE: "value",
  3515. };
  3516. const isValueQuotesOpen = (context) => {
  3517. return context.bufferValue !== "" && context.valueQuoteMark !== null;
  3518. };
  3519. const collectStyle = (context) => {
  3520. context.styles.push({
  3521. property: context.bufferProperty.trim(),
  3522. value: context.bufferValue.trim(),
  3523. });
  3524. context.bufferProperty = "";
  3525. context.bufferValue = "";
  3526. };
  3527. const processPropertyToken = (context, styleBlock, token) => {
  3528. const { value: tokenValue } = token;
  3529. switch (token.type) {
  3530. case TOKEN_TYPE.WORD:
  3531. if (context.bufferProperty.length > 0) {
  3532. throw new Error(
  3533. `Invalid style property in style block: '${styleBlock}'`
  3534. );
  3535. }
  3536. context.bufferProperty += tokenValue;
  3537. break;
  3538. case TOKEN_TYPE.MARK:
  3539. if (tokenValue === COLON) {
  3540. if (context.bufferProperty.trim().length === 0) {
  3541. throw new Error(
  3542. `Missing style property before ':' in style block: '${styleBlock}'`
  3543. );
  3544. }
  3545. context.bufferProperty = context.bufferProperty.trim();
  3546. context.processing = DECLARATION_PART.VALUE;
  3547. } else if (WHITE_SPACE_CHARACTERS.includes(tokenValue));
  3548. else {
  3549. throw new Error(
  3550. `Invalid style declaration in style block: '${styleBlock}'`
  3551. );
  3552. }
  3553. break;
  3554. default:
  3555. throw new Error(
  3556. `Unsupported style property character: '${tokenValue}' in style block: '${styleBlock}'`
  3557. );
  3558. }
  3559. };
  3560. const processValueToken = (context, styleBlock, token) => {
  3561. const { value: tokenValue } = token;
  3562. if (token.type === TOKEN_TYPE.WORD) {
  3563. context.bufferValue += tokenValue;
  3564. } else {
  3565. switch (tokenValue) {
  3566. case COLON:
  3567. if (!isValueQuotesOpen(context)) {
  3568. throw new Error(
  3569. `Invalid style value for property '${context.bufferProperty}' in style block: '${styleBlock}'`
  3570. );
  3571. }
  3572. context.bufferValue += tokenValue;
  3573. break;
  3574. case SEMICOLON:
  3575. if (isValueQuotesOpen(context)) {
  3576. context.bufferValue += tokenValue;
  3577. } else {
  3578. collectStyle(context);
  3579. context.processing = DECLARATION_PART.PROPERTY;
  3580. }
  3581. break;
  3582. case SINGLE_QUOTE:
  3583. case DOUBLE_QUOTE:
  3584. if (context.valueQuoteMark === null) {
  3585. context.valueQuoteMark = tokenValue;
  3586. } else if (
  3587. !context.bufferValue.endsWith(BACKSLASH) &&
  3588. context.valueQuoteMark === tokenValue
  3589. ) {
  3590. context.valueQuoteMark = null;
  3591. }
  3592. context.bufferValue += tokenValue;
  3593. break;
  3594. case BACKSLASH:
  3595. if (!isValueQuotesOpen(context)) {
  3596. throw new Error(
  3597. `Invalid style value for property '${context.bufferProperty}' in style block: '${styleBlock}'`
  3598. );
  3599. }
  3600. context.bufferValue += tokenValue;
  3601. break;
  3602. case SPACE:
  3603. case TAB:
  3604. case CARRIAGE_RETURN:
  3605. case LINE_FEED:
  3606. case FORM_FEED:
  3607. if (context.bufferValue.length > 0) {
  3608. context.bufferValue += tokenValue;
  3609. }
  3610. break;
  3611. default:
  3612. throw new Error(`Unknown style declaration token: '${tokenValue}'`);
  3613. }
  3614. }
  3615. };
  3616. const parseStyleBlock = (rawStyleBlock) => {
  3617. const styleBlock = rawStyleBlock.trim();
  3618. const tokens = tokenizeStyleBlock(styleBlock);
  3619. const context = {
  3620. processing: DECLARATION_PART.PROPERTY,
  3621. styles: [],
  3622. bufferProperty: "",
  3623. bufferValue: "",
  3624. valueQuoteMark: null,
  3625. };
  3626. let i = 0;
  3627. while (i < tokens.length) {
  3628. const token = tokens[i];
  3629. if (!token) {
  3630. break;
  3631. }
  3632. if (context.processing === DECLARATION_PART.PROPERTY) {
  3633. processPropertyToken(context, styleBlock, token);
  3634. } else if (context.processing === DECLARATION_PART.VALUE) {
  3635. processValueToken(context, styleBlock, token);
  3636. } else {
  3637. throw new Error("Style declaration parsing failed");
  3638. }
  3639. i += 1;
  3640. }
  3641. if (isValueQuotesOpen(context)) {
  3642. throw new Error(
  3643. `Unbalanced style declaration quotes in style block: '${styleBlock}'`
  3644. );
  3645. }
  3646. if (context.bufferProperty.length > 0) {
  3647. if (context.bufferValue.length === 0) {
  3648. throw new Error(
  3649. `Missing style value for property '${context.bufferProperty}' in style block '${styleBlock}'`
  3650. );
  3651. }
  3652. collectStyle(context);
  3653. }
  3654. if (context.styles.length === 0) {
  3655. throw new Error(STYLE_ERROR_PREFIX.NO_STYLE);
  3656. }
  3657. return context.styles;
  3658. };
  3659. const getLeftCurlyBracketIndexes = (cssRule) => {
  3660. const indexes = [];
  3661. for (let i = 0; i < cssRule.length; i += 1) {
  3662. if (cssRule[i] === BRACKET.CURLY.LEFT) {
  3663. indexes.push(i);
  3664. }
  3665. }
  3666. return indexes;
  3667. };
  3668. const parseRule = (rawCssRule, extCssDoc) => {
  3669. var _rawRuleData$selector;
  3670. const cssRule = rawCssRule.trim();
  3671. if (
  3672. cssRule.includes(`${SLASH}${ASTERISK}`) &&
  3673. cssRule.includes(`${ASTERISK}${SLASH}`)
  3674. ) {
  3675. throw new Error(STYLE_ERROR_PREFIX.NO_COMMENT);
  3676. }
  3677. const leftCurlyBracketIndexes = getLeftCurlyBracketIndexes(cssRule);
  3678. if (getFirst(leftCurlyBracketIndexes) === 0) {
  3679. throw new Error(NO_SELECTOR_ERROR_PREFIX);
  3680. }
  3681. let selectorData;
  3682. if (
  3683. leftCurlyBracketIndexes.length > 0 &&
  3684. !cssRule.includes(BRACKET.CURLY.RIGHT)
  3685. ) {
  3686. throw new Error(
  3687. `${STYLE_ERROR_PREFIX.NO_STYLE} OR ${STYLE_ERROR_PREFIX.UNCLOSED_STYLE}`
  3688. );
  3689. }
  3690. if (
  3691. leftCurlyBracketIndexes.length === 0 ||
  3692. !cssRule.includes(BRACKET.CURLY.RIGHT)
  3693. ) {
  3694. try {
  3695. selectorData = parseSelectorRulePart(cssRule, extCssDoc);
  3696. if (selectorData.success) {
  3697. var _selectorData$stylesO;
  3698. if (
  3699. ((_selectorData$stylesO = selectorData.stylesOfSelector) === null ||
  3700. _selectorData$stylesO === void 0
  3701. ? void 0
  3702. : _selectorData$stylesO.length) === 0
  3703. ) {
  3704. throw new Error(STYLE_ERROR_PREFIX.NO_STYLE_OR_REMOVE);
  3705. }
  3706. return {
  3707. selector: selectorData.selector.trim(),
  3708. ast: selectorData.ast,
  3709. rawStyles: selectorData.stylesOfSelector,
  3710. };
  3711. } else {
  3712. throw new Error("Invalid selector");
  3713. }
  3714. } catch (e) {
  3715. throw new Error(getErrorMessage(e));
  3716. }
  3717. }
  3718. let selectorBuffer;
  3719. let styleBlockBuffer;
  3720. const rawRuleData = {
  3721. selector: "",
  3722. };
  3723. for (let i = leftCurlyBracketIndexes.length - 1; i > -1; i -= 1) {
  3724. const index = leftCurlyBracketIndexes[i];
  3725. if (!index) {
  3726. throw new Error(
  3727. `Impossible to continue, no '{' to process for rule: '${cssRule}'`
  3728. );
  3729. }
  3730. selectorBuffer = cssRule.slice(0, index);
  3731. styleBlockBuffer = cssRule.slice(index + 1, cssRule.length - 1);
  3732. selectorData = parseSelectorRulePart(selectorBuffer, extCssDoc);
  3733. if (selectorData.success) {
  3734. var _rawRuleData$rawStyle;
  3735. rawRuleData.selector = selectorData.selector.trim();
  3736. rawRuleData.ast = selectorData.ast;
  3737. rawRuleData.rawStyles = selectorData.stylesOfSelector;
  3738. const parsedStyles = parseStyleBlock(styleBlockBuffer);
  3739. (_rawRuleData$rawStyle = rawRuleData.rawStyles) === null ||
  3740. _rawRuleData$rawStyle === void 0
  3741. ? void 0
  3742. : _rawRuleData$rawStyle.push(...parsedStyles);
  3743. break;
  3744. } else {
  3745. continue;
  3746. }
  3747. }
  3748. if (
  3749. ((_rawRuleData$selector = rawRuleData.selector) === null ||
  3750. _rawRuleData$selector === void 0
  3751. ? void 0
  3752. : _rawRuleData$selector.length) === 0
  3753. ) {
  3754. throw new Error("Selector in not valid");
  3755. }
  3756. return rawRuleData;
  3757. };
  3758. const parseRules$1 = (rawCssRules, extCssDoc) => {
  3759. const rawResults = createRawResultsMap();
  3760. const warnings = [];
  3761. const uniqueRules = [...new Set(rawCssRules.map((r) => r.trim()))];
  3762. uniqueRules.forEach((rule) => {
  3763. try {
  3764. saveToRawResults(rawResults, parseRule(rule, extCssDoc));
  3765. } catch (e) {
  3766. const errorMessage = getErrorMessage(e);
  3767. warnings.push(`'${rule}' - error: '${errorMessage}'`);
  3768. }
  3769. });
  3770. if (warnings.length > 0) {
  3771. logger.info(`Invalid rules:\n ${warnings.join("\n ")}`);
  3772. }
  3773. return combineRulesData(rawResults);
  3774. };
  3775. const REGEXP_DECLARATION_END = /[;}]/g;
  3776. const REGEXP_DECLARATION_DIVIDER = /[;:}]/g;
  3777. const REGEXP_NON_WHITESPACE = /\S/g;
  3778. const restoreRuleAcc = (context) => {
  3779. context.rawRuleData = {
  3780. selector: "",
  3781. };
  3782. };
  3783. const parseSelectorPart = (context, extCssDoc) => {
  3784. let selector = context.selectorBuffer.trim();
  3785. if (selector.startsWith(AT_RULE_MARKER)) {
  3786. throw new Error(`${NO_AT_RULE_ERROR_PREFIX}: '${selector}'.`);
  3787. }
  3788. let removeSelectorData;
  3789. try {
  3790. removeSelectorData = parseRemoveSelector(selector);
  3791. } catch (e) {
  3792. logger.error(getErrorMessage(e));
  3793. throw new Error(`${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${selector}'`);
  3794. }
  3795. if (context.nextIndex === -1) {
  3796. if (selector === removeSelectorData.selector) {
  3797. throw new Error(
  3798. `${STYLE_ERROR_PREFIX.NO_STYLE_OR_REMOVE}: '${context.cssToParse}'`
  3799. );
  3800. }
  3801. context.cssToParse = "";
  3802. }
  3803. let stylesOfSelector = [];
  3804. let success = false;
  3805. let ast;
  3806. try {
  3807. selector = removeSelectorData.selector;
  3808. stylesOfSelector = removeSelectorData.stylesOfSelector;
  3809. ast = extCssDoc.getSelectorAst(selector);
  3810. success = true;
  3811. } catch (e) {
  3812. success = false;
  3813. }
  3814. if (context.nextIndex > 0) {
  3815. context.cssToParse = context.cssToParse.slice(context.nextIndex);
  3816. }
  3817. return {
  3818. success,
  3819. selector,
  3820. ast,
  3821. stylesOfSelector,
  3822. };
  3823. };
  3824. const parseUntilClosingBracket = (context, styles) => {
  3825. REGEXP_DECLARATION_DIVIDER.lastIndex = context.nextIndex;
  3826. let match = REGEXP_DECLARATION_DIVIDER.exec(context.cssToParse);
  3827. if (match === null) {
  3828. throw new Error(
  3829. `${STYLE_ERROR_PREFIX.INVALID_STYLE}: '${context.cssToParse}'`
  3830. );
  3831. }
  3832. let matchPos = match.index;
  3833. let matched = match[0];
  3834. if (matched === BRACKET.CURLY.RIGHT) {
  3835. const declarationChunk = context.cssToParse.slice(
  3836. context.nextIndex,
  3837. matchPos
  3838. );
  3839. if (declarationChunk.trim().length === 0) {
  3840. if (styles.length === 0) {
  3841. throw new Error(
  3842. `${STYLE_ERROR_PREFIX.NO_STYLE}: '${context.cssToParse}'`
  3843. );
  3844. }
  3845. } else {
  3846. throw new Error(
  3847. `${STYLE_ERROR_PREFIX.INVALID_STYLE}: '${context.cssToParse}'`
  3848. );
  3849. }
  3850. return matchPos;
  3851. }
  3852. if (matched === COLON) {
  3853. const colonIndex = matchPos;
  3854. REGEXP_DECLARATION_END.lastIndex = colonIndex;
  3855. match = REGEXP_DECLARATION_END.exec(context.cssToParse);
  3856. if (match === null) {
  3857. throw new Error(
  3858. `${STYLE_ERROR_PREFIX.UNCLOSED_STYLE}: '${context.cssToParse}'`
  3859. );
  3860. }
  3861. matchPos = match.index;
  3862. matched = match[0];
  3863. const property = context.cssToParse
  3864. .slice(context.nextIndex, colonIndex)
  3865. .trim();
  3866. if (property.length === 0) {
  3867. throw new Error(
  3868. `${STYLE_ERROR_PREFIX.NO_PROPERTY}: '${context.cssToParse}'`
  3869. );
  3870. }
  3871. const value = context.cssToParse.slice(colonIndex + 1, matchPos).trim();
  3872. if (value.length === 0) {
  3873. throw new Error(
  3874. `${STYLE_ERROR_PREFIX.NO_VALUE}: '${context.cssToParse}'`
  3875. );
  3876. }
  3877. styles.push({
  3878. property,
  3879. value,
  3880. });
  3881. if (matched === BRACKET.CURLY.RIGHT) {
  3882. return matchPos;
  3883. }
  3884. }
  3885. context.cssToParse = context.cssToParse.slice(matchPos + 1);
  3886. context.nextIndex = 0;
  3887. return parseUntilClosingBracket(context, styles);
  3888. };
  3889. const parseNextStyle = (context) => {
  3890. const styles = [];
  3891. const styleEndPos = parseUntilClosingBracket(context, styles);
  3892. REGEXP_NON_WHITESPACE.lastIndex = styleEndPos + 1;
  3893. const match = REGEXP_NON_WHITESPACE.exec(context.cssToParse);
  3894. if (match === null) {
  3895. context.cssToParse = "";
  3896. return styles;
  3897. }
  3898. const matchPos = match.index;
  3899. context.cssToParse = context.cssToParse.slice(matchPos);
  3900. return styles;
  3901. };
  3902. const parseStylesheet = (rawStylesheet, extCssDoc) => {
  3903. const stylesheet = rawStylesheet.trim();
  3904. if (
  3905. stylesheet.includes(`${SLASH}${ASTERISK}`) &&
  3906. stylesheet.includes(`${ASTERISK}${SLASH}`)
  3907. ) {
  3908. throw new Error(
  3909. `${STYLE_ERROR_PREFIX.NO_COMMENT} in stylesheet: '${stylesheet}'`
  3910. );
  3911. }
  3912. const context = {
  3913. isSelector: true,
  3914. nextIndex: 0,
  3915. cssToParse: stylesheet,
  3916. selectorBuffer: "",
  3917. rawRuleData: {
  3918. selector: "",
  3919. },
  3920. };
  3921. const rawResults = createRawResultsMap();
  3922. let selectorData;
  3923. while (context.cssToParse) {
  3924. if (context.isSelector) {
  3925. context.nextIndex = context.cssToParse.indexOf(BRACKET.CURLY.LEFT);
  3926. if (context.selectorBuffer.length === 0 && context.nextIndex === 0) {
  3927. throw new Error(
  3928. `${STYLE_ERROR_PREFIX.NO_SELECTOR}: '${context.cssToParse}'`
  3929. );
  3930. }
  3931. if (context.nextIndex === -1) {
  3932. context.selectorBuffer = context.cssToParse;
  3933. } else {
  3934. context.selectorBuffer += context.cssToParse.slice(
  3935. 0,
  3936. context.nextIndex
  3937. );
  3938. }
  3939. selectorData = parseSelectorPart(context, extCssDoc);
  3940. if (selectorData.success) {
  3941. context.rawRuleData.selector = selectorData.selector.trim();
  3942. context.rawRuleData.ast = selectorData.ast;
  3943. context.rawRuleData.rawStyles = selectorData.stylesOfSelector;
  3944. context.isSelector = false;
  3945. if (context.nextIndex === -1) {
  3946. saveToRawResults(rawResults, context.rawRuleData);
  3947. restoreRuleAcc(context);
  3948. } else {
  3949. context.nextIndex = 1;
  3950. context.selectorBuffer = "";
  3951. }
  3952. } else {
  3953. context.selectorBuffer += BRACKET.CURLY.LEFT;
  3954. context.cssToParse = context.cssToParse.slice(1);
  3955. }
  3956. } else {
  3957. var _context$rawRuleData$;
  3958. const parsedStyles = parseNextStyle(context);
  3959. (_context$rawRuleData$ = context.rawRuleData.rawStyles) === null ||
  3960. _context$rawRuleData$ === void 0
  3961. ? void 0
  3962. : _context$rawRuleData$.push(...parsedStyles);
  3963. saveToRawResults(rawResults, context.rawRuleData);
  3964. context.nextIndex = 0;
  3965. restoreRuleAcc(context);
  3966. context.isSelector = true;
  3967. }
  3968. }
  3969. return combineRulesData(rawResults);
  3970. };
  3971. const isNumber = (arg) => {
  3972. return typeof arg === "number" && !Number.isNaN(arg);
  3973. };
  3974. class ThrottleWrapper {
  3975. constructor(callback) {
  3976. this.callback = callback;
  3977. this.executeCallback = this.executeCallback.bind(this);
  3978. }
  3979. executeCallback() {
  3980. this.lastRunTime = performance.now();
  3981. if (isNumber(this.timerId)) {
  3982. clearTimeout(this.timerId);
  3983. delete this.timerId;
  3984. }
  3985. this.callback();
  3986. }
  3987. run() {
  3988. if (isNumber(this.timerId)) {
  3989. return;
  3990. }
  3991. if (isNumber(this.lastRunTime)) {
  3992. const elapsedTime = performance.now() - this.lastRunTime;
  3993. if (elapsedTime < ThrottleWrapper.THROTTLE_DELAY_MS) {
  3994. this.timerId = window.setTimeout(
  3995. this.executeCallback,
  3996. ThrottleWrapper.THROTTLE_DELAY_MS - elapsedTime
  3997. );
  3998. return;
  3999. }
  4000. }
  4001. this.timerId = window.setTimeout(this.executeCallback);
  4002. }
  4003. }
  4004. _defineProperty(ThrottleWrapper, "THROTTLE_DELAY_MS", 150);
  4005. const LAST_EVENT_TIMEOUT_MS = 10;
  4006. const IGNORED_EVENTS = ["mouseover", "mouseleave", "mouseenter", "mouseout"];
  4007. const SUPPORTED_EVENTS = [
  4008. "keydown",
  4009. "keypress",
  4010. "keyup",
  4011. "auxclick",
  4012. "click",
  4013. "contextmenu",
  4014. "dblclick",
  4015. "mousedown",
  4016. "mouseenter",
  4017. "mouseleave",
  4018. "mousemove",
  4019. "mouseover",
  4020. "mouseout",
  4021. "mouseup",
  4022. "pointerlockchange",
  4023. "pointerlockerror",
  4024. "select",
  4025. "wheel",
  4026. ];
  4027. const SAFARI_PROBLEMATIC_EVENTS = ["wheel"];
  4028. class EventTracker {
  4029. constructor() {
  4030. _defineProperty(this, "getLastEventType", () => this.lastEventType);
  4031. _defineProperty(this, "getTimeSinceLastEvent", () => {
  4032. if (!this.lastEventTime) {
  4033. return null;
  4034. }
  4035. return Date.now() - this.lastEventTime;
  4036. });
  4037. this.trackedEvents = isSafariBrowser
  4038. ? SUPPORTED_EVENTS.filter(
  4039. (event) => !SAFARI_PROBLEMATIC_EVENTS.includes(event)
  4040. )
  4041. : SUPPORTED_EVENTS;
  4042. this.trackedEvents.forEach((eventName) => {
  4043. document.documentElement.addEventListener(
  4044. eventName,
  4045. this.trackEvent,
  4046. true
  4047. );
  4048. });
  4049. }
  4050. trackEvent(event) {
  4051. this.lastEventType = event.type;
  4052. this.lastEventTime = Date.now();
  4053. }
  4054. isIgnoredEventType() {
  4055. const lastEventType = this.getLastEventType();
  4056. const sinceLastEventTime = this.getTimeSinceLastEvent();
  4057. return (
  4058. !!lastEventType &&
  4059. IGNORED_EVENTS.includes(lastEventType) &&
  4060. !!sinceLastEventTime &&
  4061. sinceLastEventTime < LAST_EVENT_TIMEOUT_MS
  4062. );
  4063. }
  4064. stopTracking() {
  4065. this.trackedEvents.forEach((eventName) => {
  4066. document.documentElement.removeEventListener(
  4067. eventName,
  4068. this.trackEvent,
  4069. true
  4070. );
  4071. });
  4072. }
  4073. }
  4074. function shouldIgnoreMutations(mutations) {
  4075. return !mutations.some((m) => m.type !== "attributes");
  4076. }
  4077. function observeDocument(context) {
  4078. if (context.isDomObserved) {
  4079. return;
  4080. }
  4081. context.isDomObserved = true;
  4082. context.domMutationObserver = new natives.MutationObserver((mutations) => {
  4083. if (!mutations || mutations.length === 0) {
  4084. return;
  4085. }
  4086. const eventTracker = new EventTracker();
  4087. if (
  4088. eventTracker.isIgnoredEventType() &&
  4089. shouldIgnoreMutations(mutations)
  4090. ) {
  4091. return;
  4092. }
  4093. context.eventTracker = eventTracker;
  4094. context.scheduler.run();
  4095. });
  4096. context.domMutationObserver.observe(document, {
  4097. childList: true,
  4098. subtree: true,
  4099. attributes: true,
  4100. attributeFilter: ["id", "class"],
  4101. });
  4102. }
  4103. function disconnectDocument(context) {
  4104. if (!context.isDomObserved) {
  4105. return;
  4106. }
  4107. context.isDomObserved = false;
  4108. if (context.domMutationObserver) {
  4109. context.domMutationObserver.disconnect();
  4110. }
  4111. if (context.eventTracker) {
  4112. context.eventTracker.stopTracking();
  4113. }
  4114. }
  4115. const CONTENT_ATTR_PREFIX_REGEXP = /^("|')adguard.+?/;
  4116. const removeElement = (context, affectedElement) => {
  4117. const { node } = affectedElement;
  4118. affectedElement.removed = true;
  4119. const elementSelector = getElementSelectorPath(node);
  4120. const elementRemovalsCounter =
  4121. context.removalsStatistic[elementSelector] || 0;
  4122. if (elementRemovalsCounter > MAX_STYLE_PROTECTION_COUNT) {
  4123. logger.error(
  4124. `ExtendedCss: infinite loop protection for selector: '${elementSelector}'`
  4125. );
  4126. return;
  4127. }
  4128. if (node.parentElement) {
  4129. node.parentElement.removeChild(node);
  4130. context.removalsStatistic[elementSelector] = elementRemovalsCounter + 1;
  4131. }
  4132. };
  4133. const setStyleToElement = (node, style) => {
  4134. if (!(node instanceof HTMLElement)) {
  4135. return;
  4136. }
  4137. Object.keys(style).forEach((prop) => {
  4138. if (typeof node.style.getPropertyValue(prop.toString()) !== "undefined") {
  4139. let value = style[prop];
  4140. if (!value) {
  4141. return;
  4142. }
  4143. if (
  4144. prop === CONTENT_CSS_PROPERTY &&
  4145. value.match(CONTENT_ATTR_PREFIX_REGEXP)
  4146. ) {
  4147. return;
  4148. }
  4149. value = removeSuffix(value.trim(), "!important").trim();
  4150. node.style.setProperty(prop, value, "important");
  4151. }
  4152. });
  4153. };
  4154. const isIAffectedElement = (affectedElement) => {
  4155. return (
  4156. "node" in affectedElement &&
  4157. "rules" in affectedElement &&
  4158. affectedElement.rules instanceof Array
  4159. );
  4160. };
  4161. const isAffectedElement = (affectedElement) => {
  4162. return (
  4163. "node" in affectedElement &&
  4164. "originalStyle" in affectedElement &&
  4165. "rules" in affectedElement &&
  4166. affectedElement.rules instanceof Array
  4167. );
  4168. };
  4169. const applyStyle = (context, rawAffectedElement) => {
  4170. if (rawAffectedElement.protectionObserver) {
  4171. return;
  4172. }
  4173. let affectedElement;
  4174. if (context.beforeStyleApplied) {
  4175. if (!isIAffectedElement(rawAffectedElement)) {
  4176. throw new Error(
  4177. "Returned IAffectedElement should have 'node' and 'rules' properties"
  4178. );
  4179. }
  4180. affectedElement = context.beforeStyleApplied(rawAffectedElement);
  4181. if (!affectedElement) {
  4182. throw new Error(
  4183. "Callback 'beforeStyleApplied' should return IAffectedElement"
  4184. );
  4185. }
  4186. } else {
  4187. affectedElement = rawAffectedElement;
  4188. }
  4189. if (!isAffectedElement(affectedElement)) {
  4190. throw new Error(
  4191. "Returned IAffectedElement should have 'node' and 'rules' properties"
  4192. );
  4193. }
  4194. const { node, rules } = affectedElement;
  4195. for (let i = 0; i < rules.length; i += 1) {
  4196. const rule = rules[i];
  4197. const selector =
  4198. rule === null || rule === void 0 ? void 0 : rule.selector;
  4199. const style = rule === null || rule === void 0 ? void 0 : rule.style;
  4200. const debug = rule === null || rule === void 0 ? void 0 : rule.debug;
  4201. if (style) {
  4202. if (style[REMOVE_PSEUDO_MARKER] === PSEUDO_PROPERTY_POSITIVE_VALUE) {
  4203. removeElement(context, affectedElement);
  4204. return;
  4205. }
  4206. setStyleToElement(node, style);
  4207. } else if (!debug) {
  4208. throw new Error(
  4209. `No style declaration in rule for selector: '${selector}'`
  4210. );
  4211. }
  4212. }
  4213. };
  4214. const revertStyle = (affectedElement) => {
  4215. if (affectedElement.protectionObserver) {
  4216. affectedElement.protectionObserver.disconnect();
  4217. }
  4218. affectedElement.node.style.cssText = affectedElement.originalStyle;
  4219. };
  4220. class ExtMutationObserver {
  4221. constructor(protectionCallback) {
  4222. this.styleProtectionCount = 0;
  4223. this.observer = new natives.MutationObserver((mutations) => {
  4224. if (!mutations.length) {
  4225. return;
  4226. }
  4227. this.styleProtectionCount += 1;
  4228. protectionCallback(mutations, this);
  4229. });
  4230. }
  4231. observe(target, options) {
  4232. if (this.styleProtectionCount < MAX_STYLE_PROTECTION_COUNT) {
  4233. this.observer.observe(target, options);
  4234. } else {
  4235. logger.error("ExtendedCss: infinite loop protection for style");
  4236. }
  4237. }
  4238. disconnect() {
  4239. this.observer.disconnect();
  4240. }
  4241. }
  4242. const PROTECTION_OBSERVER_OPTIONS = {
  4243. attributes: true,
  4244. attributeOldValue: true,
  4245. attributeFilter: ["style"],
  4246. };
  4247. const createProtectionCallback = (styles) => {
  4248. const protectionCallback = (mutations, extObserver) => {
  4249. if (!mutations[0]) {
  4250. return;
  4251. }
  4252. const { target } = mutations[0];
  4253. extObserver.disconnect();
  4254. styles.forEach((style) => {
  4255. setStyleToElement(target, style);
  4256. });
  4257. extObserver.observe(target, PROTECTION_OBSERVER_OPTIONS);
  4258. };
  4259. return protectionCallback;
  4260. };
  4261. const protectStyleAttribute = (node, rules) => {
  4262. if (!natives.MutationObserver) {
  4263. return null;
  4264. }
  4265. const styles = [];
  4266. rules.forEach((ruleData) => {
  4267. const { style } = ruleData;
  4268. if (style) {
  4269. styles.push(style);
  4270. }
  4271. });
  4272. const protectionObserver = new ExtMutationObserver(
  4273. createProtectionCallback(styles)
  4274. );
  4275. protectionObserver.observe(node, PROTECTION_OBSERVER_OPTIONS);
  4276. return protectionObserver;
  4277. };
  4278. const STATS_DECIMAL_DIGITS_COUNT = 4;
  4279. class TimingStats {
  4280. constructor() {
  4281. this.appliesTimings = [];
  4282. this.appliesCount = 0;
  4283. this.timingsSum = 0;
  4284. this.meanTiming = 0;
  4285. this.squaredSum = 0;
  4286. this.standardDeviation = 0;
  4287. }
  4288. push(elapsedTimeMs) {
  4289. this.appliesTimings.push(elapsedTimeMs);
  4290. this.appliesCount += 1;
  4291. this.timingsSum += elapsedTimeMs;
  4292. this.meanTiming = this.timingsSum / this.appliesCount;
  4293. this.squaredSum += elapsedTimeMs * elapsedTimeMs;
  4294. this.standardDeviation = Math.sqrt(
  4295. this.squaredSum / this.appliesCount - Math.pow(this.meanTiming, 2)
  4296. );
  4297. }
  4298. }
  4299. const beautifyTimingNumber = (timestamp) => {
  4300. return Number(timestamp.toFixed(STATS_DECIMAL_DIGITS_COUNT));
  4301. };
  4302. const beautifyTimings = (rawTimings) => {
  4303. return {
  4304. appliesTimings: rawTimings.appliesTimings.map((t) =>
  4305. beautifyTimingNumber(t)
  4306. ),
  4307. appliesCount: beautifyTimingNumber(rawTimings.appliesCount),
  4308. timingsSum: beautifyTimingNumber(rawTimings.timingsSum),
  4309. meanTiming: beautifyTimingNumber(rawTimings.meanTiming),
  4310. standardDeviation: beautifyTimingNumber(rawTimings.standardDeviation),
  4311. };
  4312. };
  4313. const printTimingInfo = (context) => {
  4314. if (context.areTimingsPrinted) {
  4315. return;
  4316. }
  4317. context.areTimingsPrinted = true;
  4318. const timingsLogData = {};
  4319. context.parsedRules.forEach((ruleData) => {
  4320. if (ruleData.timingStats) {
  4321. const { selector, style, debug, matchedElements } = ruleData;
  4322. if (!style && !debug) {
  4323. throw new Error(
  4324. `Rule should have style declaration for selector: '${selector}'`
  4325. );
  4326. }
  4327. const selectorData = {
  4328. selectorParsed: selector,
  4329. timings: beautifyTimings(ruleData.timingStats),
  4330. };
  4331. if (
  4332. style &&
  4333. style[REMOVE_PSEUDO_MARKER] === PSEUDO_PROPERTY_POSITIVE_VALUE
  4334. ) {
  4335. selectorData.removed = true;
  4336. } else {
  4337. selectorData.styleApplied = style || null;
  4338. selectorData.matchedElements = matchedElements;
  4339. }
  4340. timingsLogData[selector] = selectorData;
  4341. }
  4342. });
  4343. if (Object.keys(timingsLogData).length === 0) {
  4344. return;
  4345. }
  4346. logger.info(
  4347. "[ExtendedCss] Timings in milliseconds for %o:\n%o",
  4348. window.location.href,
  4349. timingsLogData
  4350. );
  4351. };
  4352. const findAffectedElement = (affElements, domNode) => {
  4353. return affElements.find((affEl) => affEl.node === domNode);
  4354. };
  4355. const applyRule = (context, ruleData) => {
  4356. const isDebuggingMode = !!ruleData.debug || context.debug;
  4357. let startTime;
  4358. if (isDebuggingMode) {
  4359. startTime = performance.now();
  4360. }
  4361. const { ast } = ruleData;
  4362. const nodes = [];
  4363. try {
  4364. nodes.push(...selectElementsByAst(ast));
  4365. } catch (e) {
  4366. if (context.debug) {
  4367. logger.error(getErrorMessage(e));
  4368. }
  4369. }
  4370. nodes.forEach((node) => {
  4371. let affectedElement = findAffectedElement(context.affectedElements, node);
  4372. if (affectedElement) {
  4373. affectedElement.rules.push(ruleData);
  4374. applyStyle(context, affectedElement);
  4375. } else {
  4376. const originalStyle = node.style.cssText;
  4377. affectedElement = {
  4378. node,
  4379. rules: [ruleData],
  4380. originalStyle,
  4381. protectionObserver: null,
  4382. };
  4383. applyStyle(context, affectedElement);
  4384. context.affectedElements.push(affectedElement);
  4385. }
  4386. });
  4387. if (isDebuggingMode && startTime) {
  4388. const elapsedTimeMs = performance.now() - startTime;
  4389. if (!ruleData.timingStats) {
  4390. ruleData.timingStats = new TimingStats();
  4391. }
  4392. ruleData.timingStats.push(elapsedTimeMs);
  4393. }
  4394. return nodes;
  4395. };
  4396. const applyRules = (context) => {
  4397. const newSelectedElements = [];
  4398. disconnectDocument(context);
  4399. context.parsedRules.forEach((ruleData) => {
  4400. const nodes = applyRule(context, ruleData);
  4401. Array.prototype.push.apply(newSelectedElements, nodes);
  4402. if (ruleData.debug) {
  4403. ruleData.matchedElements = nodes;
  4404. }
  4405. });
  4406. let affLength = context.affectedElements.length;
  4407. while (affLength) {
  4408. const affectedElement = context.affectedElements[affLength - 1];
  4409. if (!affectedElement) {
  4410. break;
  4411. }
  4412. if (!newSelectedElements.includes(affectedElement.node)) {
  4413. revertStyle(affectedElement);
  4414. context.affectedElements.splice(affLength - 1, 1);
  4415. } else if (!affectedElement.removed) {
  4416. if (!affectedElement.protectionObserver) {
  4417. affectedElement.protectionObserver = protectStyleAttribute(
  4418. affectedElement.node,
  4419. affectedElement.rules
  4420. );
  4421. }
  4422. }
  4423. affLength -= 1;
  4424. }
  4425. observeDocument(context);
  4426. printTimingInfo(context);
  4427. };
  4428. class ExtendedCss {
  4429. constructor(configuration) {
  4430. if (!configuration) {
  4431. throw new Error("ExtendedCss configuration should be provided.");
  4432. }
  4433. this.applyRulesCallbackListener =
  4434. this.applyRulesCallbackListener.bind(this);
  4435. this.context = {
  4436. beforeStyleApplied: configuration.beforeStyleApplied,
  4437. debug: false,
  4438. affectedElements: [],
  4439. isDomObserved: false,
  4440. removalsStatistic: {},
  4441. parsedRules: [],
  4442. scheduler: new ThrottleWrapper(this.applyRulesCallbackListener),
  4443. };
  4444. if (!isBrowserSupported()) {
  4445. logger.error("Browser is not supported by ExtendedCss");
  4446. return;
  4447. }
  4448. if (!configuration.styleSheet && !configuration.cssRules) {
  4449. throw new Error(
  4450. "ExtendedCss configuration should have 'styleSheet' or 'cssRules' defined."
  4451. );
  4452. }
  4453. if (configuration.styleSheet) {
  4454. try {
  4455. this.context.parsedRules.push(
  4456. ...parseStylesheet(configuration.styleSheet, extCssDocument)
  4457. );
  4458. } catch (e) {
  4459. throw new Error(
  4460. `Pass the rules as configuration.cssRules since configuration.styleSheet cannot be parsed because of: '${getErrorMessage(
  4461. e
  4462. )}'`
  4463. );
  4464. }
  4465. }
  4466. if (configuration.cssRules) {
  4467. this.context.parsedRules.push(
  4468. ...parseRules$1(configuration.cssRules, extCssDocument)
  4469. );
  4470. }
  4471. this.context.debug =
  4472. configuration.debug ||
  4473. this.context.parsedRules.some((ruleData) => {
  4474. return ruleData.debug === DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE;
  4475. });
  4476. if (
  4477. this.context.beforeStyleApplied &&
  4478. typeof this.context.beforeStyleApplied !== "function"
  4479. ) {
  4480. throw new Error(
  4481. `Invalid configuration. Type of 'beforeStyleApplied' should be a function, received: '${typeof this
  4482. .context.beforeStyleApplied}'`
  4483. );
  4484. }
  4485. }
  4486. applyRulesCallbackListener() {
  4487. applyRules(this.context);
  4488. }
  4489. init() {
  4490. nativeTextContent.setGetter();
  4491. }
  4492. apply() {
  4493. applyRules(this.context);
  4494. if (document.readyState !== "complete") {
  4495. document.addEventListener(
  4496. "DOMContentLoaded",
  4497. this.applyRulesCallbackListener,
  4498. false
  4499. );
  4500. }
  4501. }
  4502. dispose() {
  4503. disconnectDocument(this.context);
  4504. this.context.affectedElements.forEach((el) => {
  4505. revertStyle(el);
  4506. });
  4507. document.removeEventListener(
  4508. "DOMContentLoaded",
  4509. this.applyRulesCallbackListener,
  4510. false
  4511. );
  4512. }
  4513. getAffectedElements() {
  4514. return this.context.affectedElements;
  4515. }
  4516. static query(selector) {
  4517. let noTiming =
  4518. arguments.length > 1 && arguments[1] !== undefined
  4519. ? arguments[1]
  4520. : true;
  4521. if (typeof selector !== "string") {
  4522. throw new Error("Selector should be defined as a string.");
  4523. }
  4524. const start = performance.now();
  4525. try {
  4526. return extCssDocument.querySelectorAll(selector);
  4527. } finally {
  4528. const end = performance.now();
  4529. if (!noTiming) {
  4530. logger.info(
  4531. `[ExtendedCss] Elapsed: ${Math.round((end - start) * 1000)} μs.`
  4532. );
  4533. }
  4534. }
  4535. }
  4536. static validate(inputSelector) {
  4537. try {
  4538. const { selector } = parseRemoveSelector(inputSelector);
  4539. ExtendedCss.query(selector);
  4540. return {
  4541. ok: true,
  4542. error: null,
  4543. };
  4544. } catch (e) {
  4545. const error = `Error: Invalid selector: '${inputSelector}' -- ${getErrorMessage(
  4546. e
  4547. )}`;
  4548. return {
  4549. ok: false,
  4550. error,
  4551. };
  4552. }
  4553. }
  4554. }
  4555.  
  4556. function parseBRules() {
  4557. data.bRules = values.brules;
  4558. if (data.bRules.length == 0) return;
  4559. data.appliedLevel = 0;
  4560. data.bRules.forEach((br) => {
  4561. const level = bRuleParser(br);
  4562. if (level > data.appliedLevel) data.appliedLevel = level;
  4563. });
  4564. }
  4565. function canApplyCss(type) {
  4566. return (
  4567. (data.appliedLevel & (type >= 2 ? 2 : 1)) == 0 &&
  4568. data[styleBoxes[type]].length > 0
  4569. );
  4570. }
  4571.  
  4572. function cleanRules() {
  4573. if (
  4574. confirm(`是否清空存储规则 ?
  4575.  
  4576. 如果要卸载脚本,点击 确定 以后不要刷新,也不要打开任何新页面,
  4577. (如果可以)清空脚本存储(全选,删除,填 [],保存),然后删除脚本`)
  4578. ) {
  4579. const has = values.hasSave;
  4580. values.rules = {};
  4581. values.time = "0/0/0 0:0:0";
  4582. values.etags = {};
  4583. values.brules = [];
  4584. if (has.length > 0) {
  4585. has.forEach((host) => {
  4586. gmValue("set", true, `ajs_saved_styles_${host}`);
  4587. });
  4588. values.hasSave = [];
  4589. }
  4590. data.appliedCount = 0;
  4591. data.allRules = "";
  4592. data.isClean = true;
  4593. gmMenu("update");
  4594. gmMenu("export");
  4595. gmMenu("count", () => location.reload());
  4596. }
  4597. }
  4598. function reportRecord() {
  4599. let text = "";
  4600. function pushRecord(css) {
  4601. const match = cssToAbp(css);
  4602. if (match === null) return;
  4603. const [item, type, sel] = match,
  4604. count =
  4605. type % 2 === 1
  4606. ? ExtendedCss.query(sel).length
  4607. : document.querySelectorAll(sel).length;
  4608. if (count > 0) {
  4609. text += `\n! 匹配元素数量: ${count}\n${item}\n`;
  4610. }
  4611. }
  4612. if (data.bRules.length > 0) {
  4613. const levelStr = ["", "通用", "特定", "所有"];
  4614. data.bRules.forEach((br) => {
  4615. if (br.level > 0) {
  4616. text += `
  4617. ! 禁用${levelStr[br.level]}元素隐藏
  4618. ${br.rule}
  4619. `;
  4620. }
  4621. });
  4622. }
  4623. styleBoxes.forEach((box, i) => {
  4624. if (canApplyCss(i)) {
  4625. data[box]
  4626. .split("\n")
  4627. .filter((css, i, csss) => csss.indexOf(css) === i)
  4628. .forEach((css) => pushRecord(css));
  4629. }
  4630. });
  4631. if (text.length > 0) {
  4632. downUrl(
  4633. URL.createObjectURL(
  4634. new Blob([`! 应用地址: \n! ${location.href}\n${text}`])
  4635. ),
  4636. `拦截报告_${location.hostname}.txt`
  4637. );
  4638. } else {
  4639. alert("这个页面没有任何规则生效");
  4640. }
  4641. }
  4642. function switchDisabledStat() {
  4643. const disaList = values.black;
  4644. data.disabled = !disaList.includes(location.hostname);
  4645. if (data.disabled) {
  4646. disaList.push(location.hostname);
  4647. } else {
  4648. disaList.splice(disaList.indexOf(location.hostname), 1);
  4649. }
  4650. values.black = disaList;
  4651. location.reload();
  4652. }
  4653.  
  4654. function initRules(apply) {
  4655. let abpRules = {};
  4656. data.receivedRules = "";
  4657. abpRules = values.rules;
  4658. data.customRules += "\n" + getComments() + "\n";
  4659. {
  4660. onlineRules.forEach((rule) => {
  4661. const resRule = getRuleFromResource(rule.标识);
  4662. if (resRule && !abpRules[rule.标识]) abpRules[rule.标识] = resRule;
  4663. });
  4664. }
  4665. const abpKeys = Object.keys(abpRules);
  4666. abpKeys.forEach((name) => {
  4667. data.receivedRules += "\n" + abpRules[name] + "\n";
  4668. });
  4669. data.allRules = data.customRules + data.receivedRules;
  4670. if (apply) splitRules();
  4671. return data.receivedRules.length;
  4672. }
  4673. function styleInject(csss, extra) {
  4674. new ExtendedCss({
  4675. styleSheet: csss.replaceAll(/\/\* \d.+? \*\//g, ""),
  4676. }).apply();
  4677. if (!extra) addStyle(csss);
  4678. }
  4679. function styleApplyExec(type) {
  4680. if (canApplyCss(type)) styleInject(data[styleBoxes[type]], type % 2 == 1);
  4681. }
  4682. function styleApply() {
  4683. parseBRules();
  4684. if (data.appliedLevel === 3) return;
  4685. styleApplyExec(0);
  4686. styleApplyExec(2);
  4687. styleApplyExec(1);
  4688. styleApplyExec(3);
  4689. gmMenu("export", reportRecord);
  4690. }
  4691. function parseRules() {
  4692. function addRule(rule, exten) {
  4693. const [full, selector] = ruleToCss(rule);
  4694. const index = exten + (rule.generic ? 0 : 2);
  4695. const checkResult = ExtendedCss.validate(selector);
  4696. if (checkResult.ok) {
  4697. data[styleBoxes[index]] += full;
  4698. data.appliedCount++;
  4699. } else {
  4700. console.error("选择器检查错误:", rule, checkResult.error);
  4701. }
  4702. }
  4703. styleBoxes.forEach((box) => {
  4704. data[box] = "";
  4705. });
  4706. [data.styles, data.extStyles, data.selectors, data.extSelectors].forEach(
  4707. (r, t) => {
  4708. r.black
  4709. .filter((v) => !r.white.includes(v))
  4710. .forEach((s) => addRule(s, t % 2));
  4711. }
  4712. );
  4713. gmMenu("count", cleanRules);
  4714. saveCss();
  4715. if (!data.saved) styleApply();
  4716. }
  4717. function splitRules() {
  4718. const bRules = [];
  4719. data.allRules.split("\n").forEach((rule) => {
  4720. if (isBasicRule(rule)) {
  4721. const brule = bRuleSpliter(rule);
  4722. if (brule) bRules.push(brule);
  4723. } else {
  4724. const ruleObj = ruleLoader(rule);
  4725. if (typeof ruleObj !== "undefined") {
  4726. if (
  4727. ruleObj.black === "black" &&
  4728. data[dataBoxes[ruleObj.type]].white.includes(ruleObj)
  4729. )
  4730. return;
  4731. data[dataBoxes[ruleObj.type]][ruleObj.black].push(ruleObj);
  4732. }
  4733. }
  4734. });
  4735. values.brules = bRules;
  4736. parseRules();
  4737. }
  4738.  
  4739. function makeInitMenu() {
  4740. gmMenu("count", cleanRules);
  4741. {
  4742. gmMenu("update", () =>
  4743. __awaiter(this, void 0, void 0, function* () {
  4744. yield performUpdate(true);
  4745. location.reload();
  4746. })
  4747. );
  4748. }
  4749. }
  4750. function storeRule(rule, resp) {
  4751. let savedRules = {};
  4752. savedRules = values.rules;
  4753. if (resp.responseText) {
  4754. let parsed = resp.responseText;
  4755. if (rule.筛选后存储) {
  4756. parsed = resp.responseText
  4757. .split("\n")
  4758. .filter((rule) => CRRE.test(rule) || isBasicRule(rule))
  4759. .join("\n");
  4760. }
  4761. savedRules[rule.标识] = parsed;
  4762. {
  4763. values.rules = savedRules;
  4764. if (values.rules[rule.标识].length !== 0) {
  4765. const etag = extrEtag(resp),
  4766. savedEtags = values.etags;
  4767. if (etag) {
  4768. savedEtags[rule.标识] = etag;
  4769. values.etags = savedEtags;
  4770. }
  4771. }
  4772. }
  4773. data.receivedRules += "\n" + savedRules[rule.标识] + "\n";
  4774. }
  4775. }
  4776. function fetchRuleBody(rule) {
  4777. var _a;
  4778. return __awaiter(this, void 0, void 0, function* () {
  4779. const getResp = yield promiseXhr({
  4780. method: "GET",
  4781. responseType: "text",
  4782. url: rule.地址,
  4783. }).catch((error) => {
  4784. console.error("规则: ", rule.地址, " 下载错误: ", error);
  4785. });
  4786. if (
  4787. (_a =
  4788. getResp === null || getResp === void 0
  4789. ? void 0
  4790. : getResp.responseText) === null || _a === void 0
  4791. ? void 0
  4792. : _a.length
  4793. ) {
  4794. storeRule(rule, getResp);
  4795. return true;
  4796. } else return false;
  4797. });
  4798. }
  4799. function fetchRule(rule) {
  4800. return new Promise((resolve, reject) =>
  4801. __awaiter(this, void 0, void 0, function* () {
  4802. var _a;
  4803. const headResp = yield promiseXhr({
  4804. method: "HEAD",
  4805. responseType: "text",
  4806. url: rule.地址,
  4807. }).catch((error) => {
  4808. console.error("规则: ", rule.地址, " HEAD 错误: ", error);
  4809. });
  4810. if (!headResp) {
  4811. reject("HEAD 失败");
  4812. } else {
  4813. const etag = extrEtag(headResp),
  4814. savedEtags = values.etags;
  4815. if (
  4816. (_a =
  4817. headResp === null || headResp === void 0
  4818. ? void 0
  4819. : headResp.responseText) === null || _a === void 0
  4820. ? void 0
  4821. : _a.length
  4822. ) {
  4823. storeRule(rule, headResp);
  4824. etag !==
  4825. (savedEtags === null || savedEtags === void 0
  4826. ? void 0
  4827. : savedEtags[rule.标识])
  4828. ? resolve()
  4829. : reject("ETag 一致");
  4830. } else {
  4831. if (
  4832. etag !==
  4833. (savedEtags === null || savedEtags === void 0
  4834. ? void 0
  4835. : savedEtags[rule.标识])
  4836. ) {
  4837. (yield fetchRuleBody(rule)) ? resolve() : reject("GET 失败");
  4838. } else reject("ETag 一致");
  4839. }
  4840. }
  4841. })
  4842. );
  4843. }
  4844. function fetchRules() {
  4845. var _a;
  4846. return __awaiter(this, void 0, void 0, function* () {
  4847. const has = (_a = values.hasSave) !== null && _a !== void 0 ? _a : "";
  4848. let hasUpdate = onlineRules.length;
  4849. data.updating = true;
  4850. gmMenu("update", () => undefined);
  4851. for (const rule of onlineRules) {
  4852. if (rule.在线更新) {
  4853. yield fetchRule(rule).catch((error) => {
  4854. console.error("获取规则 ", rule, " 发生错误: ", error);
  4855. hasUpdate--;
  4856. });
  4857. } else {
  4858. hasUpdate--;
  4859. }
  4860. }
  4861. values.time = new Date().toLocaleString("zh-CN");
  4862. if (has.length > 0 && hasUpdate > 0) {
  4863. has.forEach((host) => {
  4864. if (host === location.hostname) {
  4865. initRules(true);
  4866. data.updating = false;
  4867. makeInitMenu();
  4868. } else {
  4869. const save = gmValue("get", true, `ajs_saved_styles_${host}`);
  4870. save.needUpdate = true;
  4871. gmValue("set", true, `ajs_saved_styles_${host}`, save);
  4872. }
  4873. });
  4874. } else {
  4875. data.updating = false;
  4876. makeInitMenu();
  4877. }
  4878. });
  4879. }
  4880. function performUpdate(force) {
  4881. if (data.isFrame) return Promise.reject();
  4882. return force || new Date(values.time).getDate() !== new Date().getDate()
  4883. ? fetchRules()
  4884. : Promise.resolve();
  4885. }
  4886.  
  4887. function isSiteDisabled() {
  4888. data.disabled = values.black.includes(location.hostname);
  4889. gmMenu("disable", switchDisabledStat);
  4890. return data.disabled;
  4891. }
  4892. function main() {
  4893. return __awaiter(this, void 0, void 0, function* () {
  4894. if (isSiteDisabled()) return;
  4895. if (values.hasSave.includes(location.hostname)) readCss();
  4896. saved: {
  4897. makeInitMenu();
  4898. if (data.saved) {
  4899. styleApply();
  4900. if (!data.update) break saved;
  4901. }
  4902. if (initRules(false) === 0) {
  4903. yield performUpdate(true);
  4904. initRules(true);
  4905. }
  4906. splitRules();
  4907. }
  4908. {
  4909. try {
  4910. yield performUpdate(false);
  4911. } catch (_error) {
  4912. console.warn("iframe: ", location.href, " 取消更新");
  4913. }
  4914. }
  4915. });
  4916. }
  4917. function runOnce(key, func) {
  4918. if (key in tm.unsafeWindow) return Promise.reject();
  4919. tm.unsafeWindow[key] = true;
  4920. return func();
  4921. }
  4922. {
  4923. runOnce(data.mutex, main);
  4924. }
  4925. })({
  4926. GM_info: typeof GM_info == "object" ? GM_info : {},
  4927. unsafeWindow: typeof unsafeWindow == "object" ? unsafeWindow : window,
  4928. GM_registerMenuCommand:
  4929. typeof GM_registerMenuCommand == "function"
  4930. ? GM_registerMenuCommand
  4931. : undefined,
  4932. GM_unregisterMenuCommand:
  4933. typeof GM_unregisterMenuCommand == "function"
  4934. ? GM_unregisterMenuCommand
  4935. : undefined,
  4936. GM_getValue: typeof GM_getValue == "function" ? GM_getValue : undefined,
  4937. GM_deleteValue:
  4938. typeof GM_deleteValue == "function" ? GM_deleteValue : undefined,
  4939. GM_setValue: typeof GM_setValue == "function" ? GM_setValue : undefined,
  4940. GM_addStyle: typeof GM_addStyle == "function" ? GM_addStyle : undefined,
  4941. GM_xmlhttpRequest:
  4942. typeof GM_xmlhttpRequest == "function" ? GM_xmlhttpRequest : undefined,
  4943. GM_getResourceText:
  4944. typeof GM_getResourceText == "function" ? GM_getResourceText : undefined,
  4945. });