Greasy Fork 还支持 简体中文。

AdBlock Script for WebView

Parse ABP Cosmetic rules to CSS and apply it.

目前為 2023-04-29 提交的版本,檢視 最新版本

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