套壳油猴的广告拦截脚本

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

当前为 2022-10-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name AdBlock Script for WebView
  3. // @name:zh-CN 套壳油猴的广告拦截脚本
  4. // @author Lemon399
  5. // @version 2.2.6
  6. // @description Parse ABP Cosmetic rules to CSS and apply it.
  7. // @description:zh-CN 将 ABP 中的元素隐藏规则转换为 CSS 使用
  8. // @require https://greasyfork.org/scripts/452263-extended-css/code/extended-css.js?version=1099366
  9. // @resource jiekouAD https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt
  10. // @resource CSSRule https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/CSSRule.txt
  11. // @match *://*/*
  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_xmlhttpRequest
  20. // @grant GM_getResourceText
  21. // @grant GM_addStyle
  22. // @namespace https://lemon399-bitbucket-io.vercel.app/
  23. // @source https://gitee.com/lemon399/tampermonkey-cli/tree/master/projects/abp_parse
  24. // @connect code.gitlink.org.cn
  25. // @copyright GPL-3.0
  26. // @license GPL-3.0
  27. // ==/UserScript==
  28.  
  29. (function (vm, ExtendedCss) {
  30. "use strict";
  31.  
  32. function __awaiter(thisArg, _arguments, P, generator) {
  33. function adopt(value) {
  34. return value instanceof P
  35. ? value
  36. : new P(function (resolve) {
  37. resolve(value);
  38. });
  39. }
  40. return new (P || (P = Promise))(function (resolve, reject) {
  41. function fulfilled(value) {
  42. try {
  43. step(generator.next(value));
  44. } catch (e) {
  45. reject(e);
  46. }
  47. }
  48. function rejected(value) {
  49. try {
  50. step(generator["throw"](value));
  51. } catch (e) {
  52. reject(e);
  53. }
  54. }
  55. function step(result) {
  56. result.done
  57. ? resolve(result.value)
  58. : adopt(result.value).then(fulfilled, rejected);
  59. }
  60. step((generator = generator.apply(thisArg, _arguments || [])).next());
  61. });
  62. }
  63.  
  64. const onlineRules = [];
  65. let defaultRules = `
  66. ! 没有 ## #@# #?# #@?#
  67. ! #$# #@$# #$?# #@$?# 的行和
  68. ! 开头为 ! 的行会忽略
  69. !
  70. ! 由于语法限制,内置规则中
  71. ! 一个反斜杠需要改成两个,像这样 \\
  72. !
  73. ! 若要修改地址,请注意同步修改
  74. ! 头部的 @connect @resource
  75.  
  76. `;
  77. onlineRules.push(
  78. {
  79. 标识: "jiekouAD",
  80. 地址: "https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt",
  81. 在线更新: !!1,
  82. 筛选后存储: !!1,
  83. },
  84. {
  85. 标识: "CSSRule",
  86. 地址: "https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/CSSRule.txt",
  87. 在线更新: !!1,
  88. 筛选后存储: !!0,
  89. }
  90. );
  91.  
  92. const ruleRE = [
  93. /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?##([^\s^+].*)/,
  94. /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@#([^\s+].*)/,
  95. /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#\?#([^\s].*)/,
  96. /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@\?#([^\s].*)/,
  97. /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#\$#([^\s].*)/,
  98. /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@\$#([^\s].*)/,
  99. /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#\$\?#([^\s].*)/,
  100. /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@\$\?#([^\s].*)/,
  101. ];
  102. function sleep(time) {
  103. return new Promise((resolve) => setTimeout(resolve, time));
  104. }
  105. function runNeed(condition, fn) {
  106. let ok = false;
  107. const defaultOption = {
  108. count: 20,
  109. delay: 200,
  110. failFn: () => null,
  111. };
  112. new Promise((resolve, reject) =>
  113. __awaiter(this, void 0, void 0, function* () {
  114. for (let c = 0; !ok && c < defaultOption.count; c++) {
  115. yield sleep(defaultOption.delay);
  116. ok = condition.call(null, c + 1);
  117. }
  118. ok ? resolve() : reject();
  119. })
  120. ).then(fn, defaultOption.failFn);
  121. }
  122. function findMatches(string, res) {
  123. let result = [-1, null];
  124. res.forEach((re, i) => {
  125. const match = string.match(re);
  126. if (match) result = [i, match];
  127. });
  128. return result;
  129. }
  130. function getEtag(header) {
  131. const result = findMatches(header, [
  132. /(e|E)tag: \"(\w+)\"/,
  133. // WebMonkey 系
  134. /(e|E)tag: \[\"(\w+)\"\]/,
  135. // 书签地球
  136. /(e|E)tag=\"(\w+)\"/,
  137. ]);
  138. return result[1] ? result[1][2] : null;
  139. }
  140. function makeRuleBox() {
  141. return {
  142. black: [],
  143. white: [],
  144. };
  145. }
  146. function domainChecker(domains) {
  147. const results = [],
  148. urlSuffix = /\.+?[\w-]+$/.exec(location.hostname);
  149. let mostMatch = {
  150. long: 0,
  151. result: false,
  152. };
  153. domains.forEach((domain) => {
  154. if (domain.endsWith(".*") && Array.isArray(urlSuffix)) {
  155. domain = domain.replace(".*", urlSuffix[0]);
  156. }
  157. const invert = domain[0] == "~";
  158. if (invert) domain = domain.slice(1);
  159. const result = location.hostname.endsWith(domain);
  160. results.push(result !== invert);
  161. if (result) {
  162. if (domain.length > mostMatch.long) {
  163. mostMatch = {
  164. long: domain.length,
  165. result: result !== invert,
  166. };
  167. }
  168. }
  169. });
  170. return mostMatch.long > 0 ? mostMatch.result : results.includes(true);
  171. }
  172. function hasSome(str, arr) {
  173. return arr.some((word) => str.includes(word));
  174. }
  175. function testRule(str) {
  176. return ruleRE.some((re) => re.test(str));
  177. }
  178. function ruleSpliter(rule) {
  179. const result = findMatches(rule, ruleRE),
  180. group = result[1];
  181. if (group && (!group[1] || domainChecker(group[1].split(",")))) {
  182. const sel = group.pop();
  183. if (sel) {
  184. return {
  185. black: result[0] % 2 ? "white" : "black",
  186. type: Math.floor(result[0] / 2),
  187. sel,
  188. };
  189. }
  190. }
  191. }
  192. function ruleLoader(rule) {
  193. if (
  194. hasSome(rule, [
  195. ":matches-path(",
  196. ":min-text-length(",
  197. ":watch-attr(",
  198. ":-abp-properties(",
  199. ":matches-property(",
  200. ])
  201. )
  202. return;
  203. // 如果 #$# 不包含 {} 就排除
  204. // 可以尽量排除 Snippet Filters
  205. if (/(\w|^)#\$#/.test(rule) && !/{.+}/.test(rule)) return;
  206. // ## -> #?#
  207. if (
  208. /(\w|^)#@?#/.test(rule) &&
  209. hasSome(rule, [
  210. ":has(",
  211. ":-abp-has(",
  212. "[-ext-has=",
  213. ":has-text(",
  214. "contains(",
  215. "-abp-contains(",
  216. "[-ext-contains=",
  217. "matches-css(",
  218. "[-ext-matches-css=",
  219. "matches-css-before(",
  220. "[-ext-matches-css-before=",
  221. "matches-css-after(",
  222. "[-ext-matches-css-after=",
  223. "matches-attr(",
  224. "nth-ancestor(",
  225. "upward(",
  226. "xpath(",
  227. "remove()",
  228. "not(",
  229. "if-not(",
  230. ])
  231. ) {
  232. rule = rule.replace(/(\w|^)##/, "$1#?#").replace(/(\w|^)#@#/, "$1#@?#");
  233. }
  234. // :style(...) 转换
  235. // example.com#?##id:style(color: red)
  236. // example.com#$?##id { color: red }
  237. if (rule.includes(":style(")) {
  238. rule = rule
  239. .replace(/(\w|^)##/, "$1#$#")
  240. .replace(/(\w|^)#@#/, "$1#@$#")
  241. .replace(/(\w|^)#\?#/, "$1#$?#")
  242. .replace(/(\w|^)#@\?#/, "$1#@$?#")
  243. .replace(/:style\(/, " { ")
  244. .replace(/\)$/, " }");
  245. }
  246. return ruleSpliter(rule);
  247. }
  248.  
  249. const selectors = makeRuleBox(),
  250. extSelectors = makeRuleBox(),
  251. styles = makeRuleBox(),
  252. extStyles = makeRuleBox(),
  253. values = {
  254. get black() {
  255. return gmValue("get", false, "ajs_disabled_domains", "");
  256. },
  257. set black(v) {
  258. gmValue("set", false, "ajs_disabled_domains", v);
  259. },
  260. get rules() {
  261. return gmValue("get", true, "ajs_saved_abprules", {});
  262. },
  263. set rules(v) {
  264. gmValue("set", true, "ajs_saved_abprules", v);
  265. },
  266. get time() {
  267. return gmValue("get", false, "ajs_rules_ver", "0/0/0 0:0:0");
  268. },
  269. set time(v) {
  270. gmValue("set", false, "ajs_rules_ver", v);
  271. },
  272. get etags() {
  273. return gmValue("get", true, "ajs_rules_etags", {});
  274. },
  275. set etags(v) {
  276. gmValue("set", true, "ajs_rules_etags", v);
  277. },
  278. },
  279. data = {
  280. disabled: false,
  281. updating: false,
  282. receivedRules: "",
  283. allRules: "",
  284. presetCss:
  285. " {display: none !important;width: 0 !important;height: 0 !important;} ",
  286. hideCss: "",
  287. extraCss: "",
  288. appliedCount: 0,
  289. isFrame: vm.unsafeWindow.self !== vm.unsafeWindow.top,
  290. isClean: false,
  291. mutex: "__lemon__abp__parser__$__",
  292. timeout: 5000,
  293. xTimeout: 700,
  294. },
  295. menus = {
  296. disable: {
  297. id: undefined,
  298. get text() {
  299. return data.disabled ? "在此网站启用拦截" : "在此网站禁用拦截";
  300. },
  301. },
  302. update: {
  303. id: undefined,
  304. get text() {
  305. const time = values.time;
  306. return data.updating
  307. ? "正在更新..."
  308. : `点击更新: ${time.slice(0, 1) === "0" ? "未知时间" : time}`;
  309. },
  310. },
  311. count: {
  312. id: undefined,
  313. get text() {
  314. return data.isClean
  315. ? "已清空,点击刷新重新加载规则"
  316. : `点击清空: ${data.appliedCount} / ${
  317. data.allRules.split("\n").length
  318. }`;
  319. },
  320. },
  321. };
  322. function gmMenu(name, cb) {
  323. if (
  324. typeof vm.GM_registerMenuCommand != "function" ||
  325. typeof vm.GM_unregisterMenuCommand != "function" ||
  326. data.isFrame
  327. )
  328. return;
  329. if (typeof menus[name].id != "undefined") {
  330. vm.GM_unregisterMenuCommand(menus[name].id);
  331. menus[name].id = undefined;
  332. }
  333. if (typeof cb == "function") {
  334. menus[name].id = vm.GM_registerMenuCommand(menus[name].text, cb);
  335. }
  336. }
  337. function gmValue(action, json, key, value) {
  338. switch (action) {
  339. case "get":
  340. let v;
  341. try {
  342. v = vm.GM_getValue(key, json ? JSON.stringify(value) : value);
  343. } catch (error) {
  344. return;
  345. }
  346. return json && typeof v == "string" ? JSON.parse(v) : v;
  347. case "set":
  348. try {
  349. value === null
  350. ? vm.GM_deleteValue(key)
  351. : vm.GM_setValue(key, json ? JSON.stringify(value) : value);
  352. } catch (error) {
  353. vm.GM_deleteValue(key);
  354. }
  355. break;
  356. }
  357. }
  358. function promiseXhr(details) {
  359. return __awaiter(this, void 0, void 0, function* () {
  360. let loaded = false;
  361. try {
  362. return yield new Promise((resolve, reject) => {
  363. vm.GM_xmlhttpRequest(
  364. Object.assign(
  365. {
  366. onload(e) {
  367. loaded = true;
  368. resolve(e);
  369. },
  370. onabort: reject.bind(null, "abort"),
  371. onerror(e) {
  372. reject({
  373. error: "error",
  374. resp: e,
  375. });
  376. },
  377. ontimeout: reject.bind(null, "timeout"),
  378. onreadystatechange(e_1) {
  379. // X 浏览器超时中断
  380. if (e_1.readyState === 4) {
  381. setTimeout(() => {
  382. if (!loaded)
  383. reject({
  384. error: "X timeout",
  385. resp: e_1,
  386. });
  387. }, data.xTimeout);
  388. }
  389. // Via 浏览器超时中断,不给成功状态...
  390. if (e_1.readyState === 3) {
  391. setTimeout(() => {
  392. if (!loaded)
  393. reject({
  394. error: "Via timeout",
  395. resp: e_1,
  396. });
  397. }, data.timeout);
  398. }
  399. },
  400. timeout: data.timeout,
  401. },
  402. details
  403. )
  404. );
  405. });
  406. } catch (error) {}
  407. });
  408. }
  409. function storeRule(rule, resp) {
  410. const savedRules = values.rules,
  411. savedEtags = values.etags;
  412. let parsed = "";
  413. if (resp.responseHeaders) {
  414. const etag = getEtag(resp.responseHeaders);
  415. if (etag) {
  416. savedEtags[rule.标识] = etag;
  417. values.etags = savedEtags;
  418. }
  419. }
  420. if (resp.responseText) {
  421. if (rule.筛选后存储) {
  422. resp.responseText.split("\n").forEach((rule) => {
  423. if (testRule(rule)) parsed += rule + "\n";
  424. });
  425. savedRules[rule.标识] = parsed;
  426. } else {
  427. savedRules[rule.标识] = resp.responseText;
  428. }
  429. values.rules = savedRules;
  430. if (Object.keys(values.rules).length === 0) {
  431. data.receivedRules += "\n" + savedRules[rule.标识] + "\n";
  432. }
  433. }
  434. }
  435. function fetchRuleBody(rule) {
  436. var _a;
  437. return __awaiter(this, void 0, void 0, function* () {
  438. const getResp = yield promiseXhr({
  439. method: "GET",
  440. responseType: "text",
  441. url: rule.地址,
  442. });
  443. if (
  444. getResp &&
  445. (getResp === null || getResp === void 0
  446. ? void 0
  447. : getResp.responseText) &&
  448. ((_a = getResp.responseText) === null || _a === void 0
  449. ? void 0
  450. : _a.length) > 0
  451. ) {
  452. storeRule(rule, getResp);
  453. return true;
  454. } else return false;
  455. });
  456. }
  457. function fetchRule(rule) {
  458. return new Promise((resolve, reject) =>
  459. __awaiter(this, void 0, void 0, function* () {
  460. var _a, _b, _c;
  461. const headResp = yield promiseXhr({
  462. method: "HEAD",
  463. responseType: "text",
  464. url: rule.地址,
  465. });
  466. if (!headResp) {
  467. reject("HEAD 失败");
  468. } else {
  469. if (
  470. (headResp === null || headResp === void 0
  471. ? void 0
  472. : headResp.responseText) &&
  473. ((_a = headResp.responseText) === null || _a === void 0
  474. ? void 0
  475. : _a.length) > 0
  476. ) {
  477. storeRule(rule, headResp);
  478. resolve();
  479. } else {
  480. const etag = getEtag(
  481. typeof headResp.responseHeaders == "string"
  482. ? headResp.responseHeaders
  483. : (_c = (_b = headResp).getAllResponseHeaders) === null ||
  484. _c === void 0
  485. ? void 0
  486. : _c.call(_b)
  487. ),
  488. savedEtags = values.etags;
  489. if (!etag || etag !== savedEtags[rule.标识]) {
  490. (yield fetchRuleBody(rule)) ? resolve() : reject("GET 失败");
  491. } else reject("ETag 一致");
  492. }
  493. }
  494. })
  495. );
  496. }
  497. function fetchRules(apply) {
  498. return __awaiter(this, void 0, void 0, function* () {
  499. data.updating = true;
  500. gmMenu("update", () => undefined);
  501. for (const rule of onlineRules) {
  502. if (rule.在线更新) {
  503. yield fetchRule(rule).catch((error) => {});
  504. }
  505. }
  506. values.time = new Date().toLocaleString("zh-CN");
  507. gmMenu("count", cleanRules);
  508. initRules(apply);
  509. });
  510. }
  511. function performUpdate(force, apply) {
  512. return force
  513. ? fetchRules(apply)
  514. : new Date(values.time).getDate() !== new Date().getDate()
  515. ? fetchRules(apply)
  516. : Promise.resolve();
  517. }
  518. function switchDisabledStat() {
  519. const disaList = values.black.length === 0 ? [] : values.black.split(",");
  520. data.disabled = !disaList.includes(location.hostname);
  521. if (data.disabled) {
  522. disaList.push(location.hostname);
  523. } else {
  524. disaList.splice(disaList.indexOf(location.hostname), 1);
  525. }
  526. values.black = disaList.join(",");
  527. location.reload();
  528. }
  529. function initRules(apply) {
  530. const abpRules = values.rules;
  531. if (typeof vm.GM_getResourceText == "function") {
  532. onlineRules.forEach((rule) => {
  533. let resRule;
  534. try {
  535. resRule = vm.GM_getResourceText(rule.标识);
  536. } catch (error) {
  537. resRule = "";
  538. }
  539. if (resRule && !abpRules[rule.标识]) abpRules[rule.标识] = resRule;
  540. });
  541. }
  542. const abpKeys = Object.keys(abpRules);
  543. abpKeys.forEach((name) => {
  544. data.receivedRules += "\n" + abpRules[name] + "\n";
  545. });
  546. data.allRules = defaultRules + data.receivedRules;
  547. if (abpKeys.length != 0) {
  548. data.updating = false;
  549. gmMenu("update", () =>
  550. __awaiter(this, void 0, void 0, function* () {
  551. yield performUpdate(true, false);
  552. location.reload();
  553. })
  554. );
  555. }
  556. if (apply) splitRules();
  557. return data.receivedRules.length;
  558. }
  559. function styleApply() {
  560. if (data.hideCss.length > 0) {
  561. if (typeof vm.GM_addStyle == "function") {
  562. vm.GM_addStyle(data.hideCss);
  563. } else {
  564. runNeed(
  565. () => !!document.documentElement,
  566. () => {
  567. const elem = document.createElement("style");
  568. elem.textContent = data.hideCss;
  569. document.documentElement.appendChild(elem);
  570. }
  571. );
  572. }
  573. }
  574. if (data.extraCss.length > 0) {
  575. runNeed(
  576. () => !!document.documentElement,
  577. () => new ExtendedCss({ styleSheet: data.extraCss }).apply()
  578. );
  579. }
  580. }
  581. function cleanRules() {
  582. if (confirm(`是否清空存储规则 (${Object.keys(values.rules).length}) ?`)) {
  583. values.rules = {};
  584. values.time = "0/0/0 0:0:0";
  585. values.etags = {};
  586. data.appliedCount = 0;
  587. data.allRules = "";
  588. data.isClean = true;
  589. gmMenu("update");
  590. gmMenu("count", () => location.reload());
  591. }
  592. }
  593. function parseRules() {
  594. styles.black
  595. .filter((v) => !styles.white.includes(v))
  596. .forEach((s, i, a) => {
  597. data.hideCss += `${s} `;
  598. if (i == 0) data.appliedCount += a.length;
  599. });
  600. extStyles.black
  601. .filter((v) => !extStyles.white.includes(v))
  602. .forEach((s, i, a) => {
  603. data.extraCss += `${s} `;
  604. if (i == 0) data.appliedCount += a.length;
  605. });
  606. selectors.black
  607. .filter((v) => !selectors.white.includes(v))
  608. .forEach((s, i, a) => {
  609. data.hideCss += `${i == 0 ? "" : ","}${s}`;
  610. if (i == a.length - 1) {
  611. data.hideCss += data.presetCss;
  612. data.appliedCount += a.length;
  613. }
  614. });
  615. extSelectors.black
  616. .filter((v) => !extSelectors.white.includes(v))
  617. .forEach((s, i, a) => {
  618. data.extraCss += `${i == 0 ? "" : ","}${s}`;
  619. if (i == a.length - 1) {
  620. data.extraCss += data.presetCss;
  621. data.appliedCount += a.length;
  622. }
  623. });
  624. gmMenu("count", cleanRules);
  625. styleApply();
  626. }
  627. function splitRules() {
  628. data.allRules.split("\n").forEach((rule) => {
  629. const ruleObj = ruleLoader(rule),
  630. boxes = [selectors, extSelectors, styles, extStyles];
  631. if (typeof ruleObj != "undefined") {
  632. if (
  633. ruleObj.black == "black" &&
  634. boxes[ruleObj.type].white.includes(ruleObj.sel)
  635. )
  636. return;
  637. boxes[ruleObj.type][ruleObj.black].push(ruleObj.sel);
  638. }
  639. });
  640. parseRules();
  641. }
  642. function main() {
  643. return __awaiter(this, void 0, void 0, function* () {
  644. data.disabled = values.black.split(",").includes(location.hostname);
  645. gmMenu("disable", switchDisabledStat);
  646. if (data.disabled || (initRules(false) === 0 && data.isFrame)) return;
  647. if (data.receivedRules.length === 0) yield performUpdate(true, true);
  648. splitRules();
  649. yield performUpdate(false, false);
  650. });
  651. }
  652. function runOnce(key, func) {
  653. if (key in vm.unsafeWindow) return;
  654. vm.unsafeWindow[key] = true;
  655. func();
  656. }
  657. runOnce(data.mutex, main);
  658. })(
  659. {
  660. unsafeWindow: typeof unsafeWindow == "object" ? unsafeWindow : window,
  661. GM_registerMenuCommand:
  662. typeof GM_registerMenuCommand == "function"
  663. ? GM_registerMenuCommand
  664. : undefined,
  665. GM_unregisterMenuCommand:
  666. typeof GM_unregisterMenuCommand == "function"
  667. ? GM_unregisterMenuCommand
  668. : undefined,
  669. GM_getValue: typeof GM_getValue == "function" ? GM_getValue : undefined,
  670. GM_deleteValue:
  671. typeof GM_deleteValue == "function" ? GM_deleteValue : undefined,
  672. GM_setValue: typeof GM_setValue == "function" ? GM_setValue : undefined,
  673. GM_xmlhttpRequest:
  674. typeof GM_xmlhttpRequest == "function" ? GM_xmlhttpRequest : undefined,
  675. GM_getResourceText:
  676. typeof GM_getResourceText == "function" ? GM_getResourceText : undefined,
  677. GM_addStyle: typeof GM_addStyle == "function" ? GM_addStyle : undefined,
  678. },
  679. ExtendedCss
  680. );