diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 0972c62e7..732ac52f4 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -249,6 +249,54 @@ const isGood = rule => /******************************************************************************/ +// Two distinct hostnames: +// www.example.com +// example.com +// Can be reduced to a single one: +// example.com +// Since if example.com matches, then www.example.com (or any other subdomain +// of example.com) will always match. + +function pruneHostnameArray(hostnames) { + const rootMap = new Map(); + for ( const hostname of hostnames ) { + const labels = hostname.split('.'); + let currentMap = rootMap; + let i = labels.length; + while ( i-- ) { + const label = labels[i]; + let nextMap = currentMap.get(label); + if ( nextMap === null ) { break; } + if ( nextMap === undefined ) { + if ( i === 0 ) { + currentMap.set(label, (nextMap = null)); + } else { + currentMap.set(label, (nextMap = new Map())); + } + } else if ( i === 0 ) { + currentMap.set(label, null); + } + currentMap = nextMap; + } + } + const assemble = (currentMap, currentHostname, out) => { + for ( const [ label, nextMap ] of currentMap ) { + const nextHostname = currentHostname === '' + ? label + : `${label}.${currentHostname}`; + if ( nextMap === null ) { + out.push(nextHostname); + } else { + assemble(nextMap, nextHostname, out); + } + } + return out; + }; + return assemble(rootMap, '', []); +} + +/******************************************************************************/ + async function processNetworkFilters(assetDetails, network) { const replacer = (k, v) => { if ( k.startsWith('_') ) { return; } @@ -271,6 +319,37 @@ async function processNetworkFilters(assetDetails, network) { log(`\tRejected filter count: ${network.rejectedFilterCount}`); log(`Output rule count: ${rules.length}`); + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest/RuleCondition#browser_compatibility + // isUrlFilterCaseSensitive is false by default in Firefox. It will be + // false by default in Chromium 118+. + if ( platform === 'firefox' ) { + for ( const rule of rules ) { + if ( rule.condition === undefined ) { continue; } + if ( rule.condition.urlFilter === undefined ) { + if ( rule.condition.regexFilter === undefined ) { continue; } + } + if ( rule.condition.isUrlFilterCaseSensitive === undefined ) { + rule.condition.isUrlFilterCaseSensitive = true; + } else if ( rule.condition.isUrlFilterCaseSensitive === false ) { + rule.condition.isUrlFilterCaseSensitive = undefined; + } + } + } + + // Minimize requestDomains arrays + for ( const rule of rules ) { + const condition = rule.condition; + if ( condition === undefined ) { continue; } + const requestDomains = condition.requestDomains; + if ( requestDomains === undefined ) { continue; } + const beforeCount = requestDomains.length; + condition.requestDomains = pruneHostnameArray(requestDomains); + const afterCount = condition.requestDomains.length; + if ( afterCount !== beforeCount ) { + log(`\tPruning requestDomains: from ${beforeCount} to ${afterCount}`); + } + } + const plainGood = rules.filter(rule => isGood(rule) && isRegex(rule) === false); log(`\tPlain good: ${plainGood.length}`); log(plainGood @@ -314,9 +393,11 @@ async function processNetworkFilters(assetDetails, network) { log(`\tUnsupported: ${bad.length}`); log(bad.map(rule => rule._error.map(v => `\t\t${v}`)).join('\n'), true); + const jsonIndent = platform !== 'firefox' ? 1 : undefined; + writeFile( `${rulesetDir}/main/${assetDetails.id}.json`, - `${JSON.stringify(plainGood, replacer, 1)}\n` + `${JSON.stringify(plainGood, replacer, jsonIndent)}\n` ); if ( regexes.length !== 0 ) {