Back to Repository

Easylist AdBlock

7 upvotes
By Default Upload
Before page load (document_start)

Description

Adblocker using Easylist right from InjectJS! No other extensions required.

Match Pattern

<all_urls>

Script Code

(async function () {
  const CACHE_KEY = "__injectjs_easylist_cache__";
  const CACHE_TIME_KEY = "__injectjs_easylist_time__";
  const ONE_DAY = 24 * 60 * 60 * 1000;
  const domain = location.hostname.replace(/^www\./, "");

  async function getCachedRules() {
    const cached = JSON.parse(localStorage.getItem(CACHE_KEY) || "{}");
    const lastUpdate = parseInt(localStorage.getItem(CACHE_TIME_KEY) || "0", 10);

    if (Date.now() - lastUpdate < ONE_DAY && cached.network && cached.cosmetic) {
      console.log(`✅ InjectJS Adblock: Using cached rules`);
      return cached;
    }

    console.log("🔄 InjectJS Adblock: Fetching EasyList...");
    const res = await fetch("https://easylist.to/easylist/easylist.txt");
    const text = await res.text();
    const processed = processEasyList(text);
    localStorage.setItem(CACHE_KEY, JSON.stringify(processed));
    localStorage.setItem(CACHE_TIME_KEY, Date.now().toString());
    console.log(`✅ InjectJS Adblock: Updated cache`);
    return processed;
  }

  function processEasyList(text) {
    const network = new Set();
    const cosmetic = {};
    for (const line of text.split("\n")) {
      if (line.startsWith("||") && line.includes("^")) {
        const host = line.slice(2).split("^")[0].replace(/^www\./, "");
        network.add(host);
      } else if (line.includes("##")) {
        const [ruleDomain, selector] = line.split("##");
        const key = ruleDomain || "*";
        if (!cosmetic[key]) cosmetic[key] = [];
        cosmetic[key].push(selector.trim());
      }
    }
    return { network, cosmetic };
  }

  function isAdUrl(url, adHosts) {
    try {
      const host = new URL(url).hostname.replace(/^www\./, "");
      return adHosts.has(host) || [...adHosts].some(h => host.endsWith(h));
    } catch {
      return false;
    }
  }

  const { network: adHosts, cosmetic: allCosmetic } = await getCachedRules();

  // ===== Network Monkeypatch =====
  const originalFetch = window.fetch;
  window.fetch = async function (...args) {
    if (isAdUrl(args[0], adHosts)) {
      console.log("[InjectJS Adblock] Blocked fetch:", args[0]);
      return new Response("", { status: 204 });
    }
    return originalFetch.apply(this, args);
  };

  const OriginalXHR = window.XMLHttpRequest;
  window.XMLHttpRequest = function () {
    const xhr = new OriginalXHR();
    const origOpen = xhr.open;
    xhr.open = function (method, url, ...rest) {
      if (isAdUrl(url, adHosts)) {
        console.log("[InjectJS Adblock] Blocked XHR:", url);
        return;
      }
      return origOpen.call(this, method, url, ...rest);
    };
    return xhr;
  };

  const origCreate = document.createElement;
  document.createElement = function (tag) {
    const el = origCreate.call(this, tag);
    if (tag === "script") {
      const origSet = el.setAttribute;
      el.setAttribute = function (name, value) {
        if (name === "src" && isAdUrl(value, adHosts)) {
          console.log("[InjectJS Adblock] Blocked Script:", value);
          return;
        }
        return origSet.call(this, name, value);
      };
    }
    return el;
  };

  // ===== Cosmetic Filtering Optimized =====
  const activeRules = (allCosmetic[domain] || []).concat(allCosmetic["*"] || []);
  const idSet = new Set();
  const classSet = new Set();
  const complexSelectors = [];

  for (const sel of activeRules) {
    if (/^#[A-Za-z0-9_-]+$/.test(sel)) {
      idSet.add(sel.slice(1));
    } else if (/^\.[A-Za-z0-9_-]+$/.test(sel)) {
      classSet.add(sel.slice(1));
    } else {
      complexSelectors.push(sel);
    }
  }

  const limitedComplex = complexSelectors.slice(0, 300);

  function handleNode(node) {
    if (!(node instanceof HTMLElement)) return;
    if (node.id && idSet.has(node.id)) {
      console.log("[InjectJS Adblock] Removed by ID:", node);
      node.remove();
      return;
    }
    for (const c of node.classList) {
      if (classSet.has(c)) {
        console.log("[InjectJS Adblock] Removed by Class:", node);
        node.remove();
        return;
      }
    }
    if (limitedComplex.length) {
      try {
        if (node.matches(limitedComplex.join(","))) {
          console.log("[InjectJS Adblock] Removed by Selector:", node);
          node.remove();
        }
      } catch {}
    }
  }

  // Initial pass
  const allSelectors = [
    ...[...idSet].map(id => `#${id}`),
    ...[...classSet].map(c => `.${c}`),
    ...limitedComplex
  ].join(",");
  if (allSelectors.trim()) {
    document.querySelectorAll(allSelectors).forEach(el => {
      console.log("[InjectJS Adblock] Removed initial:", el);
      el.remove();
    });
  }

  // MutationObserver (batched)
  let queue = [];
  let scheduled = false;
  const observer = new MutationObserver(mutations => {
    for (const m of mutations) {
      queue.push(...m.addedNodes);
    }
    if (!scheduled) {
      scheduled = true;
      requestAnimationFrame(() => {
        const unique = [...new Set(queue)];
        queue = [];
        unique.forEach(handleNode);
        scheduled = false;
      });
    }
  });

  observer.observe(document.documentElement, { childList: true, subtree: true });

  console.log(`✅ InjectJS Adblock active: ${adHosts.size} hosts, ${idSet.size} IDs, ${classSet.size} classes, ${limitedComplex.length} complex selectors`);
})();
Install requires the InjectJS Chrome extension. Scripts run only on sites matching the pattern above. Review code before installing any community script.