Greasy Fork

Greasy Fork is available in English.

套壳油猴的广告拦截脚本

将 ABP 元素隐藏规则转换为 CSS 并且应用

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name               AdBlock Script for WebView
// @name:zh-CN         套壳油猴的广告拦截脚本
// @author             Lemon399
// @version            2.0
// @description        Parse ABP Cosmetic rules to CSS and apply it.
// @description:zh-CN  将 ABP 元素隐藏规则转换为 CSS 并且应用
// @require            http://greasyfork.icu/scripts/452263-extended-css/code/extended-css.js?version=1099366
// @match              *://*/*
// @run-at             document-start
// @grant              GM_getValue
// @grant              GM_deleteValue
// @grant              GM_setValue
// @grant              GM_registerMenuCommand
// @grant              GM_unregisterMenuCommand
// @grant              GM_xmlhttpRequest
// @grant              GM_addStyle
// @namespace          https://lemon399-bitbucket-io.vercel.app/
// @source             https://gitee.com/lemon399/tampermonkey-cli/tree/master/projects/abp_parse
// @connect            code.gitlink.org.cn
// @copyright          GPL-3.0
// @license          GPL-3.0
// ==/UserScript==

(function (tm, ExtendedCss) {
  "use strict";

  function _interopDefaultLegacy(e) {
    return e && typeof e === "object" && "default" in e
      ? e
      : {
          default: e,
        };
  }

  var ExtendedCss__default = _interopDefaultLegacy(ExtendedCss);

  var __assign = function () {
    __assign =
      Object.assign ||
      function __assign(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
          s = arguments[i];

          for (var p in s)
            if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
        }

        return t;
      };

    return __assign.apply(this, arguments);
  };

  function __awaiter(thisArg, _arguments, P, generator) {
    function adopt(value) {
      return value instanceof P
        ? value
        : new P(function (resolve) {
            resolve(value);
          });
    }

    return new (P || (P = Promise))(function (resolve, reject) {
      function fulfilled(value) {
        try {
          step(generator.next(value));
        } catch (e) {
          reject(e);
        }
      }

      function rejected(value) {
        try {
          step(generator["throw"](value));
        } catch (e) {
          reject(e);
        }
      }

      function step(result) {
        result.done
          ? resolve(result.value)
          : adopt(result.value).then(fulfilled, rejected);
      }

      step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
  }

  function __generator(thisArg, body) {
    var _ = {
        label: 0,
        sent: function () {
          if (t[0] & 1) throw t[1];
          return t[1];
        },
        trys: [],
        ops: [],
      },
      f,
      y,
      t,
      g;
    return (
      (g = {
        next: verb(0),
        throw: verb(1),
        return: verb(2),
      }),
      typeof Symbol === "function" &&
        (g[Symbol.iterator] = function () {
          return this;
        }),
      g
    );

    function verb(n) {
      return function (v) {
        return step([n, v]);
      };
    }

    function step(op) {
      if (f) throw new TypeError("Generator is already executing.");

      while (_)
        try {
          if (
            ((f = 1),
            y &&
              (t =
                op[0] & 2
                  ? y["return"]
                  : op[0]
                  ? y["throw"] || ((t = y["return"]) && t.call(y), 0)
                  : y.next) &&
              !(t = t.call(y, op[1])).done)
          )
            return t;
          if (((y = 0), t)) op = [op[0] & 2, t.value];

          switch (op[0]) {
            case 0:
            case 1:
              t = op;
              break;

            case 4:
              _.label++;
              return {
                value: op[1],
                done: false,
              };

            case 5:
              _.label++;
              y = op[1];
              op = [0];
              continue;

            case 7:
              op = _.ops.pop();

              _.trys.pop();

              continue;

            default:
              if (
                !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
                (op[0] === 6 || op[0] === 2)
              ) {
                _ = 0;
                continue;
              }

              if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
                _.label = op[1];
                break;
              }

              if (op[0] === 6 && _.label < t[1]) {
                _.label = t[1];
                t = op;
                break;
              }

              if (t && _.label < t[2]) {
                _.label = t[2];

                _.ops.push(op);

                break;
              }

              if (t[2]) _.ops.pop();

              _.trys.pop();

              continue;
          }

          op = body.call(thisArg, _);
        } catch (e) {
          op = [6, e];
          y = 0;
        } finally {
          f = t = 0;
        }

      if (op[0] & 5) throw op[1];
      return {
        value: op[0] ? op[1] : void 0,
        done: true,
      };
    }
  }

  var onlineRules = [
      "https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt",
      "https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/abpmerge.txt",
    ],
    defaultRules =
      '\n! \u6CA1\u6709\u4E24\u4E2A # \u7684\u884C\u548C \u5F00\u5934\u4E3A ! \u7684\u884C\u4F1A\u5FFD\u7565\n! baidu.com##.ec_wise_ad\n!\n! :remove() \u4F1A\u7528 js \u79FB\u9664\u5143\u7D20\uFF0C:remove() \u5FC5\u987B\u653E\u5728\u884C\u5C3E\n! baidu.com###ad:remove()\n!\n! \u7531\u4E8E\u8BED\u6CD5\u9650\u5236\uFF0C\u5185\u7F6E\u89C4\u5219\u4E2D\n! \u4E00\u4E2A\u53CD\u659C\u6760\u9700\u8981\u6539\u6210\u4E24\u4E2A\uFF0C\u50CF\u8FD9\u6837 \\\n!\n! \u811A\u672C\u4F1A\u9996\u5148\u5C1D\u8BD5\u4ECE\u4E0A\u9762\u7684\u5730\u5740\u6570\u7EC4\u83B7\u53D6\u89C4\u5219\n! \u83B7\u53D6\u5230\u7684\u89C4\u5219\u5C06\u4F1A\u4E0E\u5185\u7F6E\u89C4\u5219\u5408\u5E76\n! \u6240\u6709\u89C4\u5219\u83B7\u53D6\u5B8C\u6BD5\u4EE5\u540E\u624D\u4F1A\u5E94\u7528\u89C4\u5219\n!\n! \u82E5\u8981\u4FEE\u6539\u5730\u5740\uFF0C\u8BF7\u6CE8\u610F\u540C\u6B65\u4FEE\u6539\u5934\u90E8\u7684 @connect \u7684\u57DF\u540D\n!2.3.1\nvercel.app#?#blockquote:has(.mymoney)\nvercel.app#?#blockquote:-abp-has(.myhoney)\nvercel.app#?#blockquote[-ext-has=".mytony"]\n!2.3.2\nvercel.app#?#blockquote:has-text(\u70E6\u607C)\nvercel.app#?#blockquote:has-text(/\u533A\u5206\\d/)\nvercel.app#?#blockquote:contains(\u6ED1\u5757)\nvercel.app#?#blockquote:-abp-contains(\u7EA2\u65E5)\nvercel.app#?#blockquote[-ext-contains="\u5A92\u4F53"]\n!2.3.3\nvercel.app#?#blockquote:matches-css(background-color: rgb\\(135, 206, 235\\))\nvercel.app#?#blockquote:matches-css(background-color: rgb\\(200, 206, 214\\))\nvercel.app#?#blockquote[-ext-matches-css="background-color: rgb\\(240, 255, 240\\)"]\nvercel.app#?#blockquote:matches-css(background-color: /^rgb\\(255,/)\n!2.3.4\nvercel.app#?#blockquote:matches-css-before(content: \u6211\u662F\u5E7F\u544A\u554A)\nvercel.app#?#blockquote[-ext-matches-css-before="content: \u6211\u662F\u5E7F\u544A\u5462"]\n!2.3.5\nvercel.app#?#blockquote:matches-css-after(content: \u6211\u662F\u5E7F\u544A\u54DF)\nvercel.app#?#blockquote[-ext-matches-css-after="content: \u6211\u662F\u5E7F\u544A\u54E6"]\n!2.3.6\nvercel.app#?#[type=range]:matches-attr("disabled")\nvercel.app#?#[type=range]:matches-attr("min"="5")\nvercel.app#?#[type=range]:matches-attr("max"="/^3/")\n!2.3.9\nvercel.app#?#[src$="up.gif"]:nth-ancestor(2)\n!2.3.10\nvercel.app#?#[src$="up2.gif"]:upward(2)\nvercel.app#?#p > em:upward(.box)\n!2.3.12\nvercel.app#?##close:xpath(../../*[1])\n!2.3.13\nvercel.app#?##remo:remove()\n!2.3.15\nvercel.app#?##not > blockquote:not(:has(.ok))\nvercel.app#?##abpnot > blockquote:not(:-abp-has(.ok))\n!2.3.16\nvercel.app#?##ifnot > blockquote:if-not(.ok)\n!2.2.4\nvercel.app#?#blockquote:has(.yes)\nvercel.app#@?#blockquote:has(.yes)\n!2.2.10\nvercel.app#$##turq { color: turquoise !important }\n!2.2.10@\nvercel.app#$##seag { color: seagreen !important }\nvercel.app#@$##seag { color: seagreen !important }\n!2.2.11\nvercel.app#$?#span:contains(\u771F\u7684\u662F) { display: none!important; }\n!2.2.11@\nvercel.app#$?#span:contains(\u771F\u4E0D\u662F) { display: none!important; }\nvercel.app#@$?#span:contains(\u771F\u4E0D\u662F) { display: none!important; }\n';
  const id = "placeholder";

  function isObj(o) {
    return (
      typeof o == "object" &&
      (o === null || o === void 0 ? void 0 : o.toString()) === "[object Object]"
    );
  }

  function runNeed(condition, fn, option, ...args) {
    let ok = false,
      sleep = (time) => {
        return new Promise((r) => setTimeout(r, time));
      },
      defaultOption = {
        count: 20,
        delay: 200,
        failFn: () => null,
      };

    if (isObj(option)) Object.assign(defaultOption, option);
    new Promise(async (resolve, reject) => {
      for (let c = 0; !ok && c < defaultOption.count; c++) {
        await sleep(defaultOption.delay);
        ok = condition.call(null, c + 1);
      }

      ok ? resolve() : reject();
    }).then(fn.bind(null, ...args), defaultOption.failFn);
  }

  `BEXT_LAST_CHECK_KEY_${id}`;

  function getName(path) {
    var reer = /\/([^\/]+)$/.exec(path);
    return reer ? reer[1] : null;
  }

  function getEtag(header) {
    var reer = /etag: \"(\w+)\"/.exec(header);
    return reer ? reer[1] : null;
  }

  function getDay(date) {
    var reer = /\/(\d{1,2}) /.exec(date);
    return reer ? parseInt(reer[1]) : 0;
  }

  function makeRuleBox() {
    return {
      black: [],
      white: [],
      apply: "",
    };
  }

  function domainChecker(domains) {
    var results = [],
      hasTLD = /\.+?[\w-]+$/,
      urlSuffix = hasTLD.exec(location.hostname);
    var invert = false,
      result = false,
      mostMatch = {
        long: 0,
        result: undefined,
      };
    domains.forEach(function (domain) {
      if (domain.endsWith(".*") && Array.isArray(urlSuffix)) {
        domain = domain.replace(".*", urlSuffix[0]);
      }

      if (domain.startsWith("~")) {
        invert = true;
        domain = domain.slice(1);
      } else invert = false;

      result = location.hostname.endsWith(domain);
      results.push(result !== invert);

      if (result) {
        if (domain.length > mostMatch.long) {
          mostMatch = {
            long: domain.length,
            result: result !== invert,
          };
        }
      }
    });
    return mostMatch.long > 0 ? mostMatch.result : results.includes(true);
  }

  function ruleChecker(matches) {
    var index = matches.findIndex(function (i) {
      return i !== null;
    });

    if (
      index >= 0 &&
      (!matches[index][1] || domainChecker(matches[index][1].split(",")))
    ) {
      return [index % 2 == 0, Math.floor(index / 2), matches[index].pop()];
    }
  }

  function ruleSpliter(rule) {
    var result = ruleChecker([
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?##([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\?#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\?#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\$#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\$#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\$\?#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\$\?#([^\s^+].*)/
      ),
    ]);

    if (result && result[2]) {
      return {
        black: result[0],
        type: result[1],
        sel: result[2],
      };
    }
  }

  var selectors = makeRuleBox(),
    extSelectors = makeRuleBox(),
    styles = makeRuleBox(),
    extStyles = makeRuleBox(),
    values = {
      get black() {
        var v = tm.GM_getValue("ajs_disabled_domains", "");
        return typeof v == "string" ? v : "";
      },

      set black(v) {
        v === null
          ? tm.GM_deleteValue("ajs_disabled_domains")
          : tm.GM_setValue("ajs_disabled_domains", v);
      },

      get rules() {
        var v = tm.GM_getValue("ajs_saved_abprules", "{}");
        return typeof v == "string" ? JSON.parse(v) : {};
      },

      set rules(v) {
        v === null
          ? tm.GM_deleteValue("ajs_saved_abprules")
          : tm.GM_setValue("ajs_saved_abprules", JSON.stringify(v));
      },

      get time() {
        var v = tm.GM_getValue("ajs_rules_ver", "0/0/0 0:0:0");
        return typeof v == "string" ? v : "0/0/0 0:0:0";
      },

      set time(v) {
        v === null
          ? tm.GM_deleteValue("ajs_rules_ver")
          : tm.GM_setValue("ajs_rules_ver", v);
      },

      get etags() {
        var v = tm.GM_getValue("ajs_rules_etags", "{}");
        return typeof v == "string" ? JSON.parse(v) : {};
      },

      set etags(v) {
        v === null
          ? tm.GM_deleteValue("ajs_rules_etags")
          : tm.GM_setValue("ajs_rules_etags", JSON.stringify(v));
      },
    },
    data = {
      disabled: false,
      updating: false,
      receivedRules: "",
      allRules: "",
      genericStyle: document.createElement("style"),
      presetCss:
        " {display: none !important;width: 0 !important;height: 0 !important;} ",
      supportedCount: 0,
      appliedCount: 0,
    },
    menus = {
      disable: {
        id: undefined,

        get text() {
          return data.disabled ? "在此网站启用拦截" : "在此网站禁用拦截";
        },
      },
      update: {
        id: undefined,

        get text() {
          var time = values.time;
          return data.updating
            ? "正在更新..."
            : "\u70B9\u51FB\u66F4\u65B0: ".concat(
                time.slice(0, 1) === "0" ? "未知时间" : time
              );
        },
      },
      count: {
        id: undefined,

        get text() {
          return "\u70B9\u51FB\u6E05\u7A7A: "
            .concat(data.appliedCount, " / ")
            .concat(data.supportedCount, " / ")
            .concat(data.allRules.split("\n").length);
        },
      },
    };

  function gmMenu(name, cb) {
    if (
      typeof tm.GM_registerMenuCommand !== "function" ||
      typeof tm.GM_unregisterMenuCommand !== "function" ||
      window.self !== window.top
    )
      return false;
    var id = menus[name].id;

    if (typeof id !== "undefined") {
      menus[name].id = tm.GM_unregisterMenuCommand(id);
    }

    if (typeof cb == "function") {
      menus[name].id = tm.GM_registerMenuCommand(menus[name].text, cb);
    }

    return typeof menus[name].id !== "undefined";
  }

  function promiseXhr(details) {
    return new Promise(function (resolve, reject) {
      tm.GM_xmlhttpRequest(
        __assign(
          {
            onload: function (e) {
              resolve(e);
            },
            onabort: reject.bind(null),
            onerror: reject.bind(null),
            ontimeout: reject.bind(null),
          },
          details
        )
      );
    });
  }

  function storeRule(name, resp) {
    var savedRules = values.rules,
      savedEtags = values.etags;

    if (resp.responseHeaders) {
      var etag = getEtag(resp.responseHeaders);

      if (etag) {
        savedEtags[name] = etag;
        values.etags = savedEtags;
      }
    }

    if (resp.responseText) {
      savedRules[name] = resp.responseText;
      values.rules = savedRules;
    }
  }

  function fetchRule(url) {
    var _this = this;

    var _a;

    var name =
      (_a = getName(url)) !== null && _a !== void 0
        ? _a
        : "".concat(url.length, ".").concat(url.slice(-5));
    return new Promise(function (resolve, reject) {
      return __awaiter(_this, void 0, void 0, function () {
        var headResp, etag, savedEtags, _a, _b;

        return __generator(this, function (_c) {
          switch (_c.label) {
            case 0:
              if (!name) reject();
              return [
                4,
                promiseXhr({
                  method: "HEAD",
                  responseType: "text",
                  url: url,
                }),
              ];

            case 1:
              headResp = _c.sent();
              if (!headResp.responseText) return [3, 2];
              storeRule(name, headResp);
              resolve();
              return [3, 5];

            case 2:
              if (!headResp.responseHeaders) return [3, 5];
              (etag = getEtag(headResp.responseHeaders)),
                (savedEtags = values.etags);
              if (!(etag !== savedEtags[name])) return [3, 4];
              _a = storeRule;
              _b = [name];
              return [
                4,
                promiseXhr({
                  method: "GET",
                  responseType: "text",
                  url: url,
                }),
              ];

            case 3:
              _a.apply(void 0, _b.concat([_c.sent()]));

              resolve();
              return [3, 5];

            case 4:
              reject();
              _c.label = 5;

            case 5:
              return [2];
          }
        });
      });
    });
  }

  function fetchRules() {
    return __awaiter(this, void 0, void 0, function () {
      var pArray;
      return __generator(this, function (_a) {
        switch (_a.label) {
          case 0:
            pArray = [];
            data.updating = true;
            gmMenu("update", fetchRules);
            onlineRules.forEach(function (url) {
              pArray.push(fetchRule(url));
            });
            return [4, Promise.allSettled(pArray)];

          case 1:
            _a.sent();

            values.time = new Date().toLocaleString("zh-CN");
            initRules();
            return [2];
        }
      });
    });
  }

  function performUpdate(force) {
    if (force) {
      return fetchRules();
    } else {
      return getDay(values.time) !== new Date().getDate()
        ? fetchRules()
        : Promise.resolve();
    }
  }

  function switchDisabledStat() {
    var disaList = values.black.split(","),
      disaResult = disaList.includes(location.hostname);
    data.disabled = !disaResult;

    if (data.disabled) {
      disaList.push(location.hostname);
    } else {
      disaList.splice(disaList.indexOf(location.hostname), 1);
    }

    values.black = disaList.join(",");
    gmMenu("disable", switchDisabledStat);
  }

  function checkDisableStat() {
    var disaResult = values.black.split(",").includes(location.hostname);
    data.disabled = disaResult;
    gmMenu("disable", switchDisabledStat);
    return disaResult;
  }

  function initRules() {
    var abpRules = values.rules,
      abpKeys = Object.keys(abpRules);
    abpKeys.forEach(function (name) {
      data.receivedRules += "\n" + abpRules[name] + "\n";
    });
    data.allRules = defaultRules + data.receivedRules;

    if (abpKeys.length !== 0) {
      data.updating = false;
      gmMenu("update", fetchRules);
    }

    return data.receivedRules.length;
  }

  function styleApply() {
    var css =
        styles.apply +
        (selectors.apply.length > 0 ? selectors.apply + data.presetCss : ""),
      ecss =
        extStyles.apply +
        (extSelectors.apply.length > 0
          ? extSelectors.apply + data.presetCss
          : "");

    if (css.length > 0) {
      if (typeof tm.GM_addStyle == "function") {
        tm.GM_addStyle(css);
      } else {
        runNeed(
          function () {
            return !!document.documentElement;
          },
          function () {
            data.genericStyle.textContent = css;
            document.documentElement.appendChild(data.genericStyle);
          }
        );
      }
    }

    if (ecss.length > 0)
      new ExtendedCss__default.default({
        styleSheet: ecss,
      }).apply();
  }

  function parseRules() {
    var _this = this;

    [selectors, extSelectors].forEach(function (obj) {
      obj.black
        .filter(function (v) {
          return !obj.white.includes(v);
        })
        .forEach(function (sel) {
          obj.apply += "".concat(obj.apply.length == 0 ? "" : ",").concat(sel);
          data.appliedCount++;
        });
    });
    [styles, extStyles].forEach(function (obj) {
      obj.black
        .filter(function (v) {
          return !obj.white.includes(v);
        })
        .forEach(function (sel) {
          obj.apply += " ".concat(sel);
          data.appliedCount++;
        });
    });
    gmMenu("count", function () {
      if (confirm("是否清空存储规则 ?")) {
        values.rules = {};
        values.time = "0/0/0 0:0:0";
        values.etags = {};
        gmMenu("update", performUpdate.bind(_this, true));
      }
    });
    styleApply();
  }

  function main() {
    return __awaiter(this, void 0, void 0, function () {
      return __generator(this, function (_a) {
        switch (_a.label) {
          case 0:
            if (checkDisableStat()) return [2];
            if (!(initRules() === 0)) return [3, 2];
            return [4, performUpdate(true)];

          case 1:
            _a.sent();

            _a.label = 2;

          case 2:
            data.allRules.split("\n").forEach(function (rule) {
              var ruleObj = ruleSpliter(rule);
              var arr = "";

              if (typeof ruleObj !== "undefined") {
                arr = ruleObj.black ? "black" : "white";

                switch (ruleObj.type) {
                  case 0:
                    selectors[arr].push(ruleObj.sel);
                    break;

                  case 1:
                    extSelectors[arr].push(ruleObj.sel);
                    break;

                  case 2:
                    styles[arr].push(ruleObj.sel);
                    break;

                  case 3:
                    extStyles[arr].push(ruleObj.sel);
                    break;
                }

                data.supportedCount++;
              }
            });
            parseRules();
            performUpdate(false);
            return [2];
        }
      });
    });
  }

  main();
})(self, ExtendedCss);