套壳油猴的广告拦截脚本

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

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

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