AdBlock Script for WebView

Parse ABP Cosmetic rules to CSS and apply it.

目前為 2022-10-07 提交的版本,檢視 最新版本

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