套壳油猴的广告拦截脚本

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

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址