Add EasyList filter support

This commit is contained in:
2026-05-16 22:07:39 -07:00
parent f4f1a7a78d
commit 46444b193b
12 changed files with 171818 additions and 228 deletions

View File

@@ -1,6 +1,82 @@
import assert from "node:assert/strict";
import test from "node:test";
import { renderPage } from "../src/archiver.mjs";
import {
getCosmeticCssForHostname,
parseFilterRules,
renderPage,
shouldBlockRequestWithRules
} from "../src/archiver.mjs";
test("parses EasyList-style network rules, exceptions, and badfilter entries", () => {
const rules = parseFilterRules(`
[Adblock Plus 2.0]
||ads.example.com^$script,third-party
@@||ads.example.com/allowed.js$script,domain=publisher.test
banner$~image
||disabled.example^$script
||disabled.example^$script,badfilter
`);
assert.equal(
shouldBlockRequestWithRules(
rules,
"https://ads.example.com/banner.js",
"script",
"www.publisher.test"
),
true
);
assert.equal(
shouldBlockRequestWithRules(
rules,
"https://ads.example.com/banner.png",
"image",
"www.publisher.test"
),
false
);
assert.equal(
shouldBlockRequestWithRules(
rules,
"https://ads.example.com/allowed.js",
"script",
"www.publisher.test"
),
false
);
assert.equal(
shouldBlockRequestWithRules(
rules,
"https://disabled.example/ad.js",
"script",
"www.publisher.test"
),
false
);
});
test("applies cosmetic filters with domain exceptions and skips unsupported procedural selectors", () => {
const rules = parseFilterRules(`
##.generic-ad
example.com##.site-ad
example.com#@#.generic-ad
~news.example.com,example.com##.except-news
example.*##.entity-ad
bad.example##div:has-text(ad)
foo.com#$#.adguard { display: none !important; }
`);
const exampleCss = getCosmeticCssForHostname(rules, "www.example.com").join("\n");
assert.doesNotMatch(exampleCss, /\.generic-ad/);
assert.match(exampleCss, /\.site-ad/);
assert.match(exampleCss, /\.except-news/);
assert.match(exampleCss, /\.entity-ad/);
const newsCss = getCosmeticCssForHostname(rules, "news.example.com").join("\n");
assert.doesNotMatch(newsCss, /\.except-news/);
assert.equal(getCosmeticCssForHostname(rules, "bad.example").length, 1);
assert.match(getCosmeticCssForHostname(rules, "foo.com").join("\n"), /\.adguard/);
});
test("renderPage serializes CSSOM-inserted style rules", async () => {
const html = `<!doctype html>

View File

@@ -81,3 +81,25 @@ test("external asset reporting parses srcset-like attributes without splitting U
"https://media.example/photos/id/master/w_240,c_limit/photo.jpg"
]);
});
test("asset inliner skips URLs blocked by the filter hook", async () => {
const blocked = [];
const inliner = new AssetInliner({
shouldBlockAsset: (url, resourceType) => {
blocked.push([url, resourceType]);
return true;
}
});
const output = await inliner.inlineHtml(`
<link rel="stylesheet" href="https://ads.example/ad.css">
<img src="https://ads.example/ad.png">
`, "https://publisher.example/article");
assert.doesNotMatch(output, /ad\.css/);
assert.match(output, /data:image\/gif;base64/);
assert.deepEqual(blocked, [
["https://ads.example/ad.css", "stylesheet"],
["https://ads.example/ad.png", "image"]
]);
});