Skip to content

Commit 6039ef2

Browse files
committed
[mv3] Generate at most two scriptlet-related files per rulesets
Related issue: uBlockOrigin/uBOL-home#557
1 parent d5793b8 commit 6039ef2

File tree

5 files changed

+244
-146
lines changed

5 files changed

+244
-146
lines changed

platform/mv3/extension/js/scripting-manager.js

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -434,29 +434,24 @@ function registerScriptlet(context, scriptletDetails) {
434434
];
435435

436436
for ( const rulesetId of rulesetsDetails.map(v => v.id) ) {
437-
const scriptletList = scriptletDetails.get(rulesetId);
438-
if ( scriptletList === undefined ) { continue; }
439-
440-
for ( const [ token, details ] of scriptletList ) {
441-
const id = `${rulesetId}.${token}`;
442-
const registered = before.get(id);
437+
const worlds = scriptletDetails.get(rulesetId);
438+
if ( worlds === undefined ) { continue; }
439+
for ( const world of Object.keys(worlds) ) {
440+
const id = `${rulesetId}.${world.toLowerCase()}`;
443441

444442
const matches = [];
445443
const excludeMatches = [];
444+
const hostnames = worlds[world];
446445
let targetHostnames = [];
447446
if ( hasBroadHostPermission ) {
448447
excludeMatches.push(...permissionRevokedMatches);
449-
if ( details.hostnames.length > 100 ) {
450-
targetHostnames = [ '*' ];
451-
} else {
452-
targetHostnames = details.hostnames;
453-
}
448+
targetHostnames = hostnames;
454449
} else if ( permissionGrantedHostnames.length !== 0 ) {
455-
if ( details.hostnames.includes('*') ) {
450+
if ( hostnames.includes('*') ) {
456451
targetHostnames = permissionGrantedHostnames;
457452
} else {
458453
targetHostnames = ut.intersectHostnameIters(
459-
details.hostnames,
454+
hostnames,
460455
permissionGrantedHostnames
461456
);
462457
}
@@ -465,16 +460,17 @@ function registerScriptlet(context, scriptletDetails) {
465460
matches.push(...ut.matchesFromHostnames(targetHostnames));
466461
normalizeMatches(matches);
467462

463+
const registered = before.get(id);
468464
before.delete(id); // Important!
469465

470466
const directive = {
471467
id,
472-
js: [ `/rulesets/scripting/scriptlet/${id}.js` ],
468+
js: [ `/rulesets/scripting/scriptlet/${world.toLowerCase()}/${rulesetId}.js` ],
473469
matches,
474470
allFrames: true,
475471
matchOriginAsFallback: true,
476472
runAt: 'document_start',
477-
world: details.world,
473+
world,
478474
};
479475
if ( excludeMatches.length !== 0 ) {
480476
directive.excludeMatches = excludeMatches;

platform/mv3/make-rulesets.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ const consoleLog = console.log;
101101
const stdOutput = [];
102102

103103
const log = (text, silent = true) => {
104+
silent = silent && text.startsWith('!!!') === false;
104105
stdOutput.push(text);
105106
if ( silent === false ) {
106107
consoleLog(text);

platform/mv3/make-scriptlets.js

Lines changed: 95 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,37 @@ import { safeReplace } from './safe-replace.js';
2727

2828
const resourceDetails = new Map();
2929
const resourceAliases = new Map();
30-
const scriptletFiles = new Map();
30+
const worldTemplate = {
31+
scriptletFunctions: new Map(),
32+
allFunctions: new Map(),
33+
args: new Map(),
34+
arglists: new Map(),
35+
hostnames: new Map(),
36+
matches: new Set(),
37+
hasEntities: false,
38+
hasAncestors: false,
39+
};
40+
const worlds = {
41+
ISOLATED: structuredClone(worldTemplate),
42+
MAIN: structuredClone(worldTemplate),
43+
};
3144

3245
/******************************************************************************/
3346

34-
function createScriptletCoreCode(scriptletToken) {
35-
const details = resourceDetails.get(scriptletToken);
36-
const components = new Map([ [ scriptletToken, details.code ] ]);
37-
const dependencies = details.dependencies && details.dependencies.slice() || [];
47+
function createScriptletCoreCode(worldDetails, resourceEntry) {
48+
const { allFunctions } = worldDetails;
49+
allFunctions.set(resourceEntry.name, resourceEntry.code);
50+
const dependencies = resourceEntry.dependencies &&
51+
resourceEntry.dependencies.slice() || [];
3852
while ( dependencies.length !== 0 ) {
3953
const token = dependencies.shift();
40-
if ( components.has(token) ) { continue; }
4154
const details = resourceDetails.get(token);
4255
if ( details === undefined ) { continue; }
43-
components.set(token, details.code);
56+
if ( allFunctions.has(details.name) ) { continue; }
57+
allFunctions.set(details.name, details.code);
4458
if ( Array.isArray(details.dependencies) === false ) { continue; }
4559
dependencies.push(...details.dependencies);
4660
}
47-
return Array.from(components.values()).join('\n\n');
4861
}
4962

5063
/******************************************************************************/
@@ -70,7 +83,8 @@ export function init() {
7083
/******************************************************************************/
7184

7285
export function reset() {
73-
scriptletFiles.clear();
86+
worlds.ISOLATED = structuredClone(worldTemplate);
87+
worlds.MAIN = structuredClone(worldTemplate);
7488
}
7589

7690
/******************************************************************************/
@@ -85,56 +99,58 @@ export function compile(assetDetails, details) {
8599
const scriptletToken = details.args[0];
86100
const resourceEntry = resourceDetails.get(scriptletToken);
87101
if ( resourceEntry === undefined ) { return; }
88-
const argsToken = JSON.stringify(details.args.slice(1));
89102
if ( resourceEntry.requiresTrust && details.trustedSource !== true ) {
90-
console.log(`Rejecting +js(${scriptletToken},${argsToken.slice(1,-1)}): ${assetDetails.id} is not trusted`);
103+
console.log(`Rejecting +js(${details.args.join()}): ${assetDetails.id} is not trusted`);
91104
return;
92105
}
93-
if ( scriptletFiles.has(scriptletToken) === false ) {
94-
scriptletFiles.set(scriptletToken, {
95-
name: resourceEntry.name,
96-
code: createScriptletCoreCode(scriptletToken),
97-
world: resourceEntry.world,
98-
args: new Map(),
99-
hostnames: new Map(),
100-
exceptions: new Map(),
101-
hasEntities: false,
102-
hasAncestors: false,
103-
matches: new Set(),
104-
});
106+
const worldDetails = worlds[resourceEntry.world];
107+
const { scriptletFunctions } = worldDetails;
108+
if ( scriptletFunctions.has(resourceEntry.name) === false ) {
109+
scriptletFunctions.set(resourceEntry.name, scriptletFunctions.size);
110+
createScriptletCoreCode(worldDetails, resourceEntry);
105111
}
106-
const scriptletDetails = scriptletFiles.get(scriptletToken);
107-
if ( scriptletDetails.args.has(argsToken) === false ) {
108-
scriptletDetails.args.set(argsToken, scriptletDetails.args.size);
112+
// Convert args to arg indices
113+
const arglist = details.args.slice();
114+
arglist[0] = scriptletFunctions.get(resourceEntry.name);
115+
for ( let i = 1; i < arglist.length; i++ ) {
116+
const arg = arglist[i];
117+
if ( worldDetails.args.has(arg) === false ) {
118+
worldDetails.args.set(arg, worldDetails.args.size);
119+
}
120+
arglist[i] = worldDetails.args.get(arg);
121+
}
122+
const arglistKey = JSON.stringify(arglist).slice(1, -1);
123+
if ( worldDetails.arglists.has(arglistKey) === false ) {
124+
worldDetails.arglists.set(arglistKey, worldDetails.arglists.size);
109125
}
110-
const iArgs = scriptletDetails.args.get(argsToken);
126+
const arglistIndex = worldDetails.arglists.get(arglistKey);
111127
if ( details.matches ) {
112128
for ( const hn of details.matches ) {
113129
const isEntity = hn.endsWith('.*') || hn.endsWith('.*>>');
114-
scriptletDetails.hasEntities ||= isEntity;
130+
worldDetails.hasEntities ||= isEntity;
115131
const isAncestor = hn.endsWith('>>')
116-
scriptletDetails.hasAncestors ||= isAncestor;
132+
worldDetails.hasAncestors ||= isAncestor;
117133
if ( isEntity || isAncestor ) {
118-
scriptletDetails.matches.clear();
119-
scriptletDetails.matches.add('*');
134+
worldDetails.matches.clear();
135+
worldDetails.matches.add('*');
120136
}
121-
if ( scriptletDetails.matches.has('*') === false ) {
122-
scriptletDetails.matches.add(hn);
137+
if ( worldDetails.matches.has('*') === false ) {
138+
worldDetails.matches.add(hn);
123139
}
124-
if ( scriptletDetails.hostnames.has(hn) === false ) {
125-
scriptletDetails.hostnames.set(hn, new Set());
140+
if ( worldDetails.hostnames.has(hn) === false ) {
141+
worldDetails.hostnames.set(hn, new Set());
126142
}
127-
scriptletDetails.hostnames.get(hn).add(iArgs);
143+
worldDetails.hostnames.get(hn).add(arglistIndex);
128144
}
129145
} else {
130-
scriptletDetails.matches.add('*');
146+
worldDetails.matches.add('*');
131147
}
132148
if ( details.excludeMatches ) {
133149
for ( const hn of details.excludeMatches ) {
134-
if ( scriptletDetails.exceptions.has(hn) === false ) {
135-
scriptletDetails.exceptions.set(hn, []);
150+
if ( worldDetails.hostnames.has(hn) === false ) {
151+
worldDetails.hostnames.set(hn, new Set());
136152
}
137-
scriptletDetails.exceptions.get(hn).push(iArgs);
153+
worldDetails.hostnames.get(hn).add(~arglistIndex);
138154
}
139155
}
140156
}
@@ -146,51 +162,57 @@ export async function commit(rulesetId, path, writeFn) {
146162
'./scriptlets/scriptlet.template.js',
147163
{ encoding: 'utf8' }
148164
);
149-
const patchHnMap = hnmap => {
150-
const out = Array.from(hnmap);
151-
out.forEach(a => {
152-
const values = Array.from(a[1]);
153-
a[1] = values.length === 1 ? values[0] : values;
154-
});
155-
return out;
156-
};
157-
const scriptletStats = [];
158-
for ( const [ name, details ] of scriptletFiles ) {
159-
let content = safeReplace(scriptletTemplate,
160-
'function $scriptletName$(){}',
161-
details.code
165+
const stats = {};
166+
for ( const world of Object.keys(worlds) ) {
167+
const worldDetails = worlds[world];
168+
const { scriptletFunctions, allFunctions, args, arglists } = worldDetails;
169+
if ( scriptletFunctions.size === 0 ) { continue; }
170+
const hostnames = Array.from(worldDetails.hostnames).toSorted((a, b) => {
171+
const d = a[0].length - b[0].length;
172+
if ( d !== 0 ) { return d; }
173+
return a[0] < b[0] ? -1 : 1;
174+
}).map(a => ([ a[0], JSON.stringify(Array.from(a[1]).map(a => JSON.parse(a))).slice(1,-1)]));
175+
let content = safeReplace(scriptletTemplate, /\$rulesetId\$/, rulesetId, 0);
176+
if ( worldDetails.hasEntities ) {
177+
content = safeReplace(content,
178+
'const $hasEntities$ = false;',
179+
'const $hasEntities$ = true;'
180+
);
181+
}
182+
if ( worldDetails.hasAncestors ) {
183+
content = safeReplace(content,
184+
'const $hasAncestors$ = false;',
185+
'const $hasAncestors$ = true;'
186+
);
187+
};
188+
content = safeReplace(content,
189+
'const $scriptletHostnames$ = [];',
190+
`const $scriptletHostnames$ = /* ${hostnames.length} */ ${JSON.stringify(hostnames.map(a => a[0]))};`
162191
);
163-
content = safeReplace(content, /\$rulesetId\$/, rulesetId, 0);
164-
content = safeReplace(content, /\$scriptletName\$/, details.name, 0);
165192
content = safeReplace(content,
166-
'self.$argsList$',
167-
JSON.stringify(Array.from(details.args.keys()).map(a => JSON.parse(a)))
193+
'const $scriptletArglistRefs$ = [];',
194+
`const $scriptletArglistRefs$ = /* ${hostnames.length} */ ${JSON.stringify(hostnames.map(a => a[1]).join(';'))};`
168195
);
169196
content = safeReplace(content,
170-
'self.$hostnamesMap$',
171-
JSON.stringify(patchHnMap(details.hostnames))
197+
'const $scriptletArglists$ = [];',
198+
`const $scriptletArglists$ = /* ${arglists.size} */ ${JSON.stringify(Array.from(arglists.keys()).join(';'))};`
172199
);
173200
content = safeReplace(content,
174-
'self.$hasEntities$',
175-
JSON.stringify(details.hasEntities)
201+
'const $scriptletArgs$ = [];',
202+
`const $scriptletArgs$ = /* ${args.size} */ ${JSON.stringify(Array.from(args.keys()).join('\n'))};`
176203
);
177204
content = safeReplace(content,
178-
'self.$hasAncestors$',
179-
JSON.stringify(details.hasAncestors)
205+
'const $scriptletFunctions$ = [];',
206+
`const $scriptletFunctions$ = /* ${scriptletFunctions.size} */\n[${Array.from(scriptletFunctions.keys()).join(',')}];`
180207
);
181208
content = safeReplace(content,
182-
'self.$exceptionsMap$',
183-
JSON.stringify(Array.from(details.exceptions))
209+
'function $scriptletCode$(){} // eslint-disable-line',
210+
Array.from(allFunctions.values()).join('\n\n')
184211
);
185-
writeFn(`${path}/${rulesetId}.${name}`, content);
186-
scriptletStats.push([
187-
name.slice(0, -3), {
188-
hostnames: Array.from(details.matches).sort(),
189-
world: details.world,
190-
}
191-
]);
212+
writeFn(`${path}/${world.toLowerCase()}/${rulesetId}.js`, content);
213+
stats[world] = Array.from(worldDetails.matches).sort();
192214
}
193-
return scriptletStats;
215+
return stats;
194216
}
195217

196218
/******************************************************************************/

0 commit comments

Comments
 (0)