diff --git a/Build/lib/fetch-retry.ts b/Build/lib/fetch-retry.ts
index c70ea10e..3def686d 100644
--- a/Build/lib/fetch-retry.ts
+++ b/Build/lib/fetch-retry.ts
@@ -23,7 +23,7 @@ if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR, { recursive: true });
}
-const agent = new Agent({ allowH2: true });
+const agent = new Agent({ allowH2: false });
setGlobalDispatcher(agent.compose(
interceptors.retry({
diff --git a/Build/lib/parse-filter/filters.ts b/Build/lib/parse-filter/filters.ts
index 2e276154..892cd217 100644
--- a/Build/lib/parse-filter/filters.ts
+++ b/Build/lib/parse-filter/filters.ts
@@ -111,10 +111,8 @@ export function processFilterRulesWithPreload(
});
}
-// const R_KNOWN_NOT_NETWORK_FILTER_PATTERN_2 = /(\$popup|\$removeparam|\$popunder|\$cname)/;
-// cname exceptional filter can not be parsed by NetworkFilter
-// Surge / Clash can't handle CNAME either, so we just ignore them
-
+// many filter that has modifiers can not work on Surge/Clash because browser context is required
+// we can early bail out those rules
const kwfilter = createKeywordFilter([
'!',
'?',
@@ -132,10 +130,16 @@ const kwfilter = createKeywordFilter([
// special modifier
'$popup',
'$removeparam',
+ '$redirect',
'$popunder',
'$cname',
'$frame',
'$domain',
+ '$from',
+ '$to',
+ '$csp',
+ '$replace',
+ '$urlskip',
// some bad syntax
'^popup'
]);
diff --git a/Build/lib/trie.test.ts b/Build/lib/trie.test.ts
index 25fede25..79381530 100644
--- a/Build/lib/trie.test.ts
+++ b/Build/lib/trie.test.ts
@@ -91,8 +91,7 @@ describe('Trie', () => {
trie.add('example.com');
trie.add('moe.sb');
- expect(trie.delete('')).toBe(false);
- expect(trie.delete('')).toBe(false);
+ expect(trie.delete('no-match.com')).toBe(false);
expect(trie.delete('example.org')).toBe(false);
expect(trie.delete('skk.moe')).toBe(true);
@@ -131,7 +130,7 @@ describe('Trie', () => {
expect(trie.find('.example.com')).toStrictEqual(['blog.example.com', 'cdn.example.com']);
expect(trie.find('org')).toStrictEqual(['example.org']);
expect(trie.find('example.net')).toStrictEqual([]);
- expect(trie.find('')).toStrictEqual(['example.com', 'example.org', 'blog.example.com', 'cdn.example.com']);
+ expect(trie.dump()).toStrictEqual(['example.com', 'example.org', 'blog.example.com', 'cdn.example.com']);
});
it('should be possible to retrieve items matching the given prefix even with a smol trie', () => {
@@ -148,7 +147,7 @@ describe('Trie', () => {
expect(trie.find('.example.com')).toStrictEqual(['.example.com']);
expect(trie.find('org')).toStrictEqual(['example.org']);
expect(trie.find('example.net')).toStrictEqual([]);
- expect(trie.find('')).toStrictEqual(['.example.com', 'example.org']);
+ expect(trie.dump()).toStrictEqual(['.example.com', 'example.org']);
});
it('should be possible to create a trie from an arbitrary iterable.', () => {
@@ -192,6 +191,28 @@ describe('surge domainset dedupe', () => {
});
describe('smol tree', () => {
+ it('should init tree', () => {
+ const trie = createTrie([
+ 'skk.moe',
+ 'anotherskk.moe',
+ 'blog.anotherskk.moe',
+ 'blog.skk.moe',
+ '.cdn.local',
+ 'blog.img.skk.local',
+ 'img.skk.local'
+ ], true);
+
+ expect(trie.dump()).toStrictEqual([
+ 'skk.moe',
+ 'anotherskk.moe',
+ '.cdn.local',
+ 'blog.skk.moe',
+ 'blog.anotherskk.moe',
+ 'img.skk.local',
+ 'blog.img.skk.local'
+ ]);
+ });
+
it('should create simple tree - 1', () => {
const trie = createTrie([
'.skk.moe', 'blog.skk.moe', '.cdn.skk.moe', 'skk.moe',
@@ -264,7 +285,7 @@ describe('smol tree', () => {
]);
});
- it('should efficiently whitelist domains', () => {
+ it('should effctly whitelist domains', () => {
const trie = createTrie([
'skk.moe',
'anotherskk.moe',
@@ -275,16 +296,6 @@ describe('smol tree', () => {
'img.skk.local'
], true);
- expect(trie.dump()).toStrictEqual([
- 'skk.moe',
- 'anotherskk.moe',
- '.cdn.local',
- 'blog.skk.moe',
- 'blog.anotherskk.moe',
- 'img.skk.local',
- 'blog.img.skk.local'
- ]);
-
trie.whitelist('.skk.moe');
expect(trie.dump()).toStrictEqual([
diff --git a/Build/lib/trie.ts b/Build/lib/trie.ts
index 0d93d27b..1c141aea 100644
--- a/Build/lib/trie.ts
+++ b/Build/lib/trie.ts
@@ -41,7 +41,7 @@ function deepTrieNodeToJSON(node: TrieNode,
const createNode = (parent: TrieNode | null = null): TrieNode => [1, parent, new Map(), null] as TrieNode;
-export function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] {
+function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] {
const tokens = hostname.split('.');
const results: string[] = [];
let token = '';
@@ -50,6 +50,8 @@ export function hostnameToTokens(hostname: string, hostnameFromIndex: number): s
token = tokens[i];
if (token.length > 0) {
results.push(token);
+ } else {
+ throw new TypeError(JSON.stringify({ hostname, hostnameFromIndex }, null, 2));
}
}
@@ -117,7 +119,9 @@ abstract class Triebase {
let parent: TrieNode = node;
let token: string;
+ let child: Map> = node[2];
+ // reverse lookup from end to start
for (let i = tokens.length - 1; i >= 0; i--) {
token = tokens[i];
@@ -127,8 +131,10 @@ abstract class Triebase {
parent = node;
- if (node[2].has(token)) {
- node = node[2].get(token)!;
+ child = node[2];
+ // cache node index access is 20% faster than direct access when doing twice
+ if (child.has(token)) {
+ node = child.get(token)!;
} else {
return null;
}
@@ -147,6 +153,8 @@ abstract class Triebase {
let node: TrieNode = this.$root;
let parent: TrieNode = node;
+ let child: Map> = node[2];
+
const onToken = (token: string) => {
// if (token === '') {
// return true;
@@ -154,8 +162,10 @@ abstract class Triebase {
parent = node;
- if (node[2].has(token)) {
- node = node[2].get(token)!;
+ child = node[2];
+
+ if (child.has(token)) {
+ node = child.get(token)!;
} else {
return null;
}
@@ -204,12 +214,14 @@ abstract class Triebase {
const node = nodeStack.shift()!;
const suffix = suffixStack.shift()!;
- if (node[2].size) {
- const keys = Array.from(node[2].keys()).sort(Triebase.compare);
+ const child = node[2];
+
+ if (child.size) {
+ const keys = Array.from(child.keys()).sort(Triebase.compare);
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i];
- const childNode = node[2].get(key)!;
+ const childNode = child.get(key)!;
// Pushing the child node to the stack for next iteration of DFS
nodeStack.push(childNode);
@@ -271,17 +283,18 @@ abstract class Triebase {
suffixStack.push(initialSuffix);
let node: TrieNode = initialNode;
+ let child: Map> = node[2];
do {
node = nodeStack.shift()!;
const suffix = suffixStack.shift()!;
-
- if (node[2].size) {
- const keys = Array.from(node[2].keys()).sort(Triebase.compare);
+ child = node[2];
+ if (child.size) {
+ const keys = Array.from(child.keys()).sort(Triebase.compare);
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i];
- const childNode = node[2].get(key)!;
+ const childNode = child.get(key)!;
// Pushing the child node to the stack for next iteration of DFS
nodeStack.push(childNode);
@@ -303,18 +316,21 @@ abstract class Triebase {
const onLoop = (node: TrieNode, parent: TrieNode, token: string) => {
// Keeping track of a potential branch to prune
- // Even if the node size is 1, but the single child is ".", we should retain the branch
- // Since the "." could be special if it is the leaf-est node
- const onlyChild = node[2].size === 0 && !node[1];
+ const child = node[2];
- if (toPrune != null) { // the top-est branch that could potentially being pruned
- if (!onlyChild) {
- // The branch has moew than single child, retain the branch.
- // And we need to abort prune the parent, so we set it to null
+ // console.log({
+ // child, parent, token
+ // });
+ // console.log(this.inspect(0));
+
+ if (toPrune !== null) { // the most near branch that could potentially being pruned
+ if (child.size > 1) {
+ // The branch has some children, the branch need retain.
+ // And we need to abort prune that parent branch, so we set it to null
toPrune = null;
tokenToPrune = null;
}
- } else if (onlyChild) {
+ } else if (child.size < 1) {
// There is only one token child, or no child at all, we can prune it safely
// It is now the top-est branch that could potentially being pruned
toPrune = parent;
@@ -552,7 +568,6 @@ export class HostnameSmolTrie extends Triebase {
public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.', hostnameFromIndex = suffix[0] === '.' ? 1 : 0) {
const tokens = hostnameToTokens(suffix, hostnameFromIndex);
const res = this.getSingleChildLeaf(tokens);
-
if (res === null) return;
const { node, toPrune, tokenToPrune } = res;
@@ -572,7 +587,7 @@ export class HostnameSmolTrie extends Triebase {
// return early if not found
if (missingBit(node[0], START)) return;
- if (tokenToPrune && toPrune) {
+ if (toPrune && tokenToPrune) {
toPrune[2].delete(tokenToPrune);
} else {
node[0] = deleteBit(node[0], START);
@@ -587,13 +602,15 @@ export class HostnameTrie extends Triebase {
add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = suffix[0] === '.' ? 1 : 0): void {
let node: TrieNode = this.$root;
+ let child: Map> = node[2];
const onToken = (token: string) => {
- if (node[2].has(token)) {
- node = node[2].get(token)!;
+ child = node[2];
+ if (child.has(token)) {
+ node = child.get(token)!;
} else {
const newNode = createNode(node);
- node[2].set(token, newNode);
+ child.set(token, newNode);
node = newNode;
}
diff --git a/Build/trace/index.ts b/Build/trace/index.ts
index 53100019..1462b508 100644
--- a/Build/trace/index.ts
+++ b/Build/trace/index.ts
@@ -120,7 +120,7 @@ export function task(importMetaMain: boolean, importMetaPath: string) {
dummySpan.traceChildAsync('dummy', (childSpan) => fn(childSpan, onCleanup)).finally(() => {
dummySpan.stop();
printTraceResult(dummySpan.traceResult);
- whyIsNodeRunning();
+ process.nextTick(whyIsNodeRunning);
});
}