From 224410a6f5d7f758f9fcc914250046ee04954dee Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 7 Sep 2022 10:15:36 -0400 Subject: [PATCH] Add per-site on/off switch to mv3 experimental version --- .github/workflows/main.yml | 10 +- Makefile | 9 +- platform/mv3/extension/background.js | 65 ------- platform/mv3/extension/css/popup.css | 227 ++++++++++++++++++++++++ platform/mv3/extension/js/background.js | 188 ++++++++++++++++++++ platform/mv3/extension/js/popup.js | 114 ++++++++++++ platform/mv3/extension/manifest.json | 15 +- platform/mv3/extension/popup.html | 57 ++++++ platform/mv3/make-rulesets.js | 24 ++- src/js/messaging.js | 7 +- src/js/static-dnr-filtering.js | 4 +- src/js/static-net-filtering.js | 13 +- tools/make-mv3.sh | 65 ++++--- 13 files changed, 692 insertions(+), 106 deletions(-) delete mode 100644 platform/mv3/extension/background.js create mode 100644 platform/mv3/extension/css/popup.css create mode 100644 platform/mv3/extension/js/background.js create mode 100644 platform/mv3/extension/js/popup.js create mode 100644 platform/mv3/extension/popup.html diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9cd1db1cb..0361fb5ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,13 +39,17 @@ jobs: tag_name: ${{ steps.release_info.outputs.VERSION }} release_name: ${{ steps.release_info.outputs.VERSION }} prerelease: true - - name: Build all packages + - name: Build MV2 packages run: | tools/make-chromium.sh ${{ steps.release_info.outputs.VERSION }} tools/make-firefox.sh ${{ steps.release_info.outputs.VERSION }} tools/make-thunderbird.sh ${{ steps.release_info.outputs.VERSION }} tools/make-npm.sh ${{ steps.release_info.outputs.VERSION }} tools/make-mv3.sh all + - name: Build MV3 packages + run: | + tools/make-mv3.sh + echo ::set-output name=MV3PACKAGE::$(basename $(ls dist/build/uBlock0_*.mv3.zip)) - name: Upload Chromium package uses: actions/upload-release-asset@v1 env: @@ -88,6 +92,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: dist/build/uBlock0.mv3.zip - asset_name: uBlock0.mv3.zip + asset_path: dist/build/${{ env.MV3PACKAGE }} + asset_name: ${{ env.MV3PACKAGE }} asset_content_type: application/octet-stream diff --git a/Makefile b/Makefile index 77af4aa02..41b600af7 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ run_options := $(filter-out $@,$(MAKECMDGOALS)) compare maxcost medcost mincost modifiers record wasm sources := $(wildcard assets/resources/* src/* src/*/* src/*/*/* src/*/*/*/*) -platform := $(wildcard platform/* platform/*/*) +platform := $(wildcard platform/* platform/*/* platform/*/*/*) assets := $(wildcard submodules/uAssets/* \ submodules/uAssets/*/* \ submodules/uAssets/*/*/* \ @@ -52,10 +52,11 @@ dig: dist/build/uBlock0.dig dig-snfe: dig cd dist/build/uBlock0.dig && npm run snfe $(run_options) -dist/build/uBlock0.mv3: tools/make-mv3.sh $(sources) $(platform) - tools/make-mv3.sh all +mv3: tools/make-mv3.sh $(sources) $(platform) + tools/make-mv3.sh -mv3: dist/build/uBlock0.mv3 +mv3-quick: tools/make-mv3.sh $(sources) $(platform) + tools/make-mv3.sh quick # Update submodules. update-submodules: diff --git a/platform/mv3/extension/background.js b/platform/mv3/extension/background.js deleted file mode 100644 index 4c7ef6cd9..000000000 --- a/platform/mv3/extension/background.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -import regexRulesets from '/rulesets/regexes.js'; - -const dnr = chrome.declarativeNetRequest; - -dnr.setExtensionActionOptions({ displayActionCountAsBadgeText: true }); - -(async ( ) => { - const allRules = []; - const toCheck = []; - for ( const regexRuleset of regexRulesets ) { - if ( regexRuleset.enabled !== true ) { continue; } - for ( const rule of regexRuleset.rules ) { - const regex = rule.condition.regexFilter; - const isCaseSensitive = rule.condition.isUrlFilterCaseSensitive === true; - allRules.push(rule); - toCheck.push(dnr.isRegexSupported({ regex, isCaseSensitive })); - } - } - const results = await Promise.all(toCheck); - const newRules = []; - for ( let i = 0; i < allRules.length; i++ ) { - const rule = allRules[i]; - const result = results[i]; - if ( result instanceof Object && result.isSupported ) { - newRules.push(rule); - } else { - console.info(`${result.reason}: ${rule.condition.regexFilter}`); - } - } - const oldRules = await dnr.getDynamicRules(); - const oldRuleMap = new Map(oldRules.map(rule => [ rule.id, rule ])); - const newRuleMap = new Map(newRules.map(rule => [ rule.id, rule ])); - const addRules = []; - const removeRuleIds = []; - for ( const oldRule of oldRules ) { - const newRule = newRuleMap.get(oldRule.id); - if ( newRule === undefined ) { - removeRuleIds.push(oldRule.id); - } else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) { - removeRuleIds.push(oldRule.id); - addRules.push(newRule); - } - } - for ( const newRule of newRuleMap.values() ) { - if ( oldRuleMap.has(newRule.id) ) { continue; } - addRules.push(newRule); - } - if ( addRules.length !== 0 || removeRuleIds.length !== 0 ) { - await dnr.updateDynamicRules({ addRules, removeRuleIds }); - } - - const dynamicRules = await dnr.getDynamicRules(); - console.log(`Dynamic rule count: ${dynamicRules.length}`); - - const enabledRulesets = await dnr.getEnabledRulesets(); - console.log(`Enabled rulesets: ${enabledRulesets}`); - - console.log(`Available dynamic rule count: ${dnr.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES - dynamicRules.length}`); - - dnr.getAvailableStaticRuleCount().then(count => { - console.log(`Available static rule count: ${count}`); - }); -})(); diff --git a/platform/mv3/extension/css/popup.css b/platform/mv3/extension/css/popup.css new file mode 100644 index 000000000..fc3eb2d2d --- /dev/null +++ b/platform/mv3/extension/css/popup.css @@ -0,0 +1,227 @@ + /* External CSS values override */ +.fa-icon.fa-icon-badged > .fa-icon-badge { + bottom: auto; + top: -20%; + } + +/* Internal CSS values */ +:root body { + overflow: hidden; + } +:root body, +:root.mobile body { + --font-size: 14px; + --popup-gap: var(--font-size); + --popup-gap-thin: calc(0.5 * var(--popup-gap)); + --popup-gap-extra-thin: calc(0.25 * var(--popup-gap)); + --popup-main-min-width: 18em; + --popup-firewall-min-width: 30em; + --popup-rule-cell-width: 5em; + font-size: var(--font-size); + line-height: 20px; + } +:root body.loading { + opacity: 0; + } +a { + color: var(--ink-1); + fill: var(--ink-1); + text-decoration: none; + } +:focus { + outline: 0; + } + +#main { + align-self: flex-start; + max-width: 340px; + min-width: var(--popup-main-min-width); + } +:root.portrait #main { + align-self: inherit; + } +hr { + border: 0; + border-top: 1px solid var(--hr-ink); + margin: 0; + padding: 0; + } + +#sticky { + background-color: var(--surface-1); + position: sticky; + top: 0; + z-index: 100; + } +#stickyTools { + align-items: stretch; + display: flex; + justify-content: space-between; + } +#switch { + color: var(--popup-power-ink); + cursor: pointer; + display: flex; + fill: var(--popup-power-ink); + flex-grow: 1; + font-size: 96px; + justify-content: center; + margin: var(--popup-gap-thin) var(--popup-gap-thin) 0; + padding: 0; + stroke: none; + stroke-width: 64; + } +body.off #switch { + fill: var(--surface-1); + stroke: var(--checkbox-ink); + } +.rulesetTools { + background-color: transparent; + border: 0; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: space-evenly; + width: 25%; + } +.rulesetTools [id] { + background-color: var(--popup-ruleset-tool-surface); + border-radius: 4px; + cursor: pointer; + fill: var(--popup-ruleset-tool-ink); + flex-grow: 1; + font-size: 2.2em; + padding: 0; + visibility: hidden; + } +.rulesetTools [id]:not(:first-of-type) { + margin-block-start: 1px; + } +.rulesetTools [id] > svg { + fill: var(--ink-4); + } +body.needReload #refresh, +body.needSave #saveRules, +body.needSave #revertRules { + visibility: visible; + } +#hostname { + margin: var(--popup-gap) var(--popup-gap-extra-thin); + text-align: center; + } +#hostname > span { + word-break: break-all; + } +#hostname > span + span { + font-weight: 600; + } + +.itemRibbon { + column-gap: var(--popup-gap); + display: grid; + grid-auto-columns: 1fr; + grid-auto-flow: column; + grid-template: auto / 1fr 1fr; + margin: var(--popup-gap); + } +.itemRibbon > span + span { + text-align: end; + } + +.toolRibbon { + align-items: start; + background-color: var(--popup-toolbar-surface); + display: grid; + grid-auto-columns: 1fr; + grid-auto-flow: column; + grid-template: auto / repeat(4, 1fr); + justify-items: center; + margin: 0; + white-space: normal; + } +.toolRibbon .tool { + cursor: pointer; + display: flex; + flex-direction: column; + font-size: 1.4em; + min-width: 32px; + padding: var(--popup-gap) + var(--popup-gap-thin); + unicode-bidi: embed; + visibility: hidden; + } +.toolRibbon .tool:hover { + color: var(--ink-1); + fill: var(--ink-1); + } +.toolRibbon .tool.enabled { + visibility: visible; + } +.toolRibbon .tool .caption { + font: 10px/12px sans-serif; + margin-top: 6px; + text-align: center; + } +body.mobile.no-tooltips .toolRibbon .tool { + font-size: 1.6em; + } + +#basicTools { + margin-top: var(--default-gap); + } + +/* configurable UI elements */ +:root:not(.mobile) .toolRibbon .caption, +:root.mobile body.no-tooltips .toolRibbon .caption, +:root.mobile body[data-ui~="-captions"] .toolRibbon .caption { + display: none; + } +:root.mobile .toolRibbon .caption, +:root:not(.mobile) body[data-ui~="+captions"] .toolRibbon .caption { + display: inherit; + } +:root:not(.mobile) .toolRibbon .tool, +:root.mobile body.no-tooltips .toolRibbon .tool, +:root.mobile body[data-ui~="-captions"] .toolRibbon .tool { + padding: var(--popup-gap) var(--popup-gap-thin); + } + +/* horizontally-constrained viewport */ +:root.portrait body { + overflow-y: auto; + width: 100%; + } +:root.portrait #main { + max-width: unset; + } +/* mouse-driven devices */ +:root.desktop { + display: flex; + justify-content: flex-end; + } +:root.desktop body { + --popup-gap: calc(var(--font-size) * 0.875); + } +:root.desktop body:not(.off) #switch:hover { + fill: rgb(var(--popup-power-ink-rgb) / 90%); + } +:root.desktop body.off #switch:hover { + stroke: var(--popup-power-ink); + } +:root.desktop .rulesetTools [id]:hover { + background-color: var(--popup-ruleset-tool-surface-hover); + } +:root.desktop .rulesetTools [id]:hover > svg { + fill: var(--ink-2); + } +:root.desktop #firewall { + direction: rtl; + line-height: 1.4; + } +:root.desktop .tool:hover { + background-color: var(--popup-toolbar-surface-hover); + } +:root.desktop #moreOrLess > span:hover { + background-color: var(--surface-2); + /* background-color: var(--popup-toolbar-surface-hover); */ + } diff --git a/platform/mv3/extension/js/background.js b/platform/mv3/extension/js/background.js new file mode 100644 index 000000000..85307962b --- /dev/null +++ b/platform/mv3/extension/js/background.js @@ -0,0 +1,188 @@ +'use strict'; + +import regexRulesets from '/rulesets/regexes.js'; + +/******************************************************************************/ + +const dnr = chrome.declarativeNetRequest; +const TRUSTED_DIRECTIVE_BASE_RULE_ID = 1000000; +const dynamicRuleMap = new Map(); + +/******************************************************************************/ + +async function updateRegexRules() { + const allRules = []; + const toCheck = []; + for ( const regexRuleset of regexRulesets ) { + if ( regexRuleset.enabled !== true ) { continue; } + for ( const rule of regexRuleset.rules ) { + const regex = rule.condition.regexFilter; + const isCaseSensitive = rule.condition.isUrlFilterCaseSensitive === true; + allRules.push(rule); + toCheck.push(dnr.isRegexSupported({ regex, isCaseSensitive })); + } + } + const results = await Promise.all(toCheck); + const newRules = []; + for ( let i = 0; i < allRules.length; i++ ) { + const rule = allRules[i]; + const result = results[i]; + if ( result instanceof Object && result.isSupported ) { + newRules.push(rule); + } else { + console.info(`${result.reason}: ${rule.condition.regexFilter}`); + } + } + const newRuleMap = new Map(newRules.map(rule => [ rule.id, rule ])); + const addRules = []; + const removeRuleIds = []; + for ( const oldRule of dynamicRuleMap.values() ) { + if ( oldRule.id >= TRUSTED_DIRECTIVE_BASE_RULE_ID ) { continue; } + const newRule = newRuleMap.get(oldRule.id); + if ( newRule === undefined ) { + removeRuleIds.push(oldRule.id); + dynamicRuleMap.delete(oldRule.id); + } else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) { + removeRuleIds.push(oldRule.id); + addRules.push(newRule); + dynamicRuleMap.set(oldRule.id, newRule); + } + } + for ( const newRule of newRuleMap.values() ) { + if ( dynamicRuleMap.has(newRule.id) ) { continue; } + addRules.push(newRule); + dynamicRuleMap.set(newRule.id, newRule); + } + if ( addRules.length !== 0 || removeRuleIds.length !== 0 ) { + return dnr.updateDynamicRules({ addRules, removeRuleIds }); + } +} + +/******************************************************************************/ + +async function matchesTrustedSiteDirective(details) { + const url = new URL(details.origin); + let rule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID); + if ( rule === undefined ) { return false; } + const domainSet = new Set(rule.condition.requestDomains); + let hostname = url.hostname; + for (;;) { + if ( domainSet.has(hostname) ) { return true; } + const pos = hostname.indexOf('.'); + if ( pos === -1 ) { break; } + hostname = hostname.slice(pos+1); + } + return false; +} + +async function addTrustedSiteDirective(details) { + const url = new URL(details.origin); + let rule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID); + if ( rule !== undefined ) { + rule.condition.initiatorDomains = undefined; + if ( Array.isArray(rule.condition.requestDomains) === false ) { + rule.condition.requestDomains = []; + } + } + if ( rule === undefined ) { + rule = { + id: TRUSTED_DIRECTIVE_BASE_RULE_ID, + action: { + type: 'allowAllRequests', + }, + condition: { + requestDomains: [ url.hostname ], + resourceTypes: [ 'main_frame' ], + }, + priority: TRUSTED_DIRECTIVE_BASE_RULE_ID, + }; + dynamicRuleMap.set(TRUSTED_DIRECTIVE_BASE_RULE_ID, rule); + } else if ( rule.condition.requestDomains.includes(url.hostname) === false ) { + rule.condition.requestDomains.push(url.hostname); + } + await dnr.updateDynamicRules({ + addRules: [ rule ], + removeRuleIds: [ TRUSTED_DIRECTIVE_BASE_RULE_ID ], + }); + return true; +} + +async function removeTrustedSiteDirective(details) { + const url = new URL(details.origin); + let rule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID); + if ( rule === undefined ) { return false; } + rule.condition.initiatorDomains = undefined; + if ( Array.isArray(rule.condition.requestDomains) === false ) { + rule.condition.requestDomains = []; + } + const domainSet = new Set(rule.condition.requestDomains); + const beforeCount = domainSet.size; + let hostname = url.hostname; + for (;;) { + domainSet.delete(hostname); + const pos = hostname.indexOf('.'); + if ( pos === -1 ) { break; } + hostname = hostname.slice(pos+1); + } + if ( domainSet.size === beforeCount ) { return false; } + if ( domainSet.size === 0 ) { + dynamicRuleMap.delete(TRUSTED_DIRECTIVE_BASE_RULE_ID); + await dnr.updateDynamicRules({ + removeRuleIds: [ TRUSTED_DIRECTIVE_BASE_RULE_ID ] + }); + return false; + } + rule.condition.requestDomains = Array.from(domainSet); + await dnr.updateDynamicRules({ + addRules: [ rule ], + removeRuleIds: [ TRUSTED_DIRECTIVE_BASE_RULE_ID ], + }); + return false; +} + +async function toggleTrustedSiteDirective(details) { + return details.state + ? removeTrustedSiteDirective(details) + : addTrustedSiteDirective(details); +} + +/******************************************************************************/ + +(async ( ) => { + const dynamicRules = await dnr.getDynamicRules(); + for ( const rule of dynamicRules ) { + dynamicRuleMap.set(rule.id, rule); + } + + await updateRegexRules(); + + console.log(`Dynamic rule count: ${dynamicRuleMap.size}`); + + const enabledRulesets = await dnr.getEnabledRulesets(); + console.log(`Enabled rulesets: ${enabledRulesets}`); + + console.log(`Available dynamic rule count: ${dnr.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES - dynamicRuleMap.size}`); + + dnr.getAvailableStaticRuleCount().then(count => { + console.log(`Available static rule count: ${count}`); + }); + + dnr.setExtensionActionOptions({ displayActionCountAsBadgeText: true }); + + chrome.runtime.onMessage.addListener((request, sender, callback) => { + switch ( request.what ) { + case 'matchesTrustedSiteDirective': + matchesTrustedSiteDirective(request).then(response => { + callback(response); + }); + return true; + case 'toggleTrustedSiteDirective': + toggleTrustedSiteDirective(request).then(response => { + callback(response); + }); + return true; + default: + break; + } + }); +})(); diff --git a/platform/mv3/extension/js/popup.js b/platform/mv3/extension/js/popup.js new file mode 100644 index 000000000..a252cf7d2 --- /dev/null +++ b/platform/mv3/extension/js/popup.js @@ -0,0 +1,114 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2014-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +/******************************************************************************/ + +let currentTab = {}; +let originalTrustedState = false; + +/******************************************************************************/ + +async function toggleTrustedSiteDirective() { + let url; + try { + url = new URL(currentTab.url); + } catch(ex) { + return; + } + if ( url instanceof URL === false ) { return; } + const targetTrustedState = document.body.classList.contains('off'); + const newTrustedState = await chrome.runtime.sendMessage({ + what: 'toggleTrustedSiteDirective', + origin: url.origin, + state: targetTrustedState, + tabId: currentTab.id, + }).catch(( ) => targetTrustedState === false); + document.body.classList.toggle('off', newTrustedState === true); + document.body.classList.toggle( + 'needReload', + newTrustedState !== originalTrustedState + ); +} + +/******************************************************************************/ + +function reloadTab(ev) { + chrome.tabs.reload(currentTab.id, { + bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey, + }); + document.body.classList.remove('needReload'); + originalTrustedState = document.body.classList.contains('off'); +} + +/******************************************************************************/ + +async function init() { + const [ tab ] = await chrome.tabs.query({ active: true }); + if ( tab instanceof Object === false ) { return true; } + currentTab = tab; + + let url; + try { + url = new URL(currentTab.url); + } catch(ex) { + } + + if ( url !== undefined ) { + originalTrustedState = await chrome.runtime.sendMessage({ + what: 'matchesTrustedSiteDirective', + origin: url.origin, + }) === true; + } + + const body = document.body; + body.classList.toggle('off', originalTrustedState); + const elemHn = document.querySelector('#hostname'); + + elemHn.textContent = url && url.hostname || ''; + + document.querySelector('#switch').addEventListener( + 'click', + toggleTrustedSiteDirective + ); + + document.querySelector('#refresh').addEventListener( + 'click', + reloadTab + ); + + document.body.classList.remove('loading'); + + return true; +} + +async function tryInit() { + try { + await init(); + } catch(ex) { + setTimeout(tryInit, 100); + } +} + +tryInit(); + +/******************************************************************************/ diff --git a/platform/mv3/extension/manifest.json b/platform/mv3/extension/manifest.json index 8a618a8bf..851314eca 100644 --- a/platform/mv3/extension/manifest.json +++ b/platform/mv3/extension/manifest.json @@ -1,7 +1,15 @@ { + "action": { + "default_icon": { + "16": "img/icon_16.png", + "32": "img/icon_32.png", + "64": "img/icon_64.png" + }, + "default_popup": "popup.html" + }, "author": "Raymond Hill", - "background": { - "service_worker": "background.js", + "background": { + "service_worker": "/js/background.js", "type": "module" }, "declarative_net_request": { @@ -19,7 +27,8 @@ "minimum_chrome_version": "101.0", "name": "uBO Minus (MV3)", "permissions": [ + "activeTab", "declarativeNetRequest" ], - "version": "0.1.0" + "version": "0.1" } diff --git a/platform/mv3/extension/popup.html b/platform/mv3/extension/popup.html new file mode 100644 index 000000000..d000a4e20 --- /dev/null +++ b/platform/mv3/extension/popup.html @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + +
+
+
+
+ lock + eraser +
+
+ + + + + + + +
+
+ refresh +
+
+
­
+
+
+ + + + + cogs +
+
+ + + + + + + diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 8635d062e..813f84bcb 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -137,6 +137,7 @@ async function main() { for ( const ruleset of rulesetConfigs ) { const lists = []; + log('============================'); log(`Listset for '${ruleset.id}':`); if ( Array.isArray(ruleset.paths) ) { @@ -152,11 +153,14 @@ async function main() { } } - const rules = await dnrRulesetFromRawLists(lists, { + const details = await dnrRulesetFromRawLists(lists, { env: [ 'chromium' ], }); - - log(`Ruleset size for '${ruleset.id}': ${rules.length}`); + const { ruleset: rules } = details; + log(`Input filter count: ${details.filterCount}`); + log(`\tAccepted filter count: ${details.acceptedFilterCount}`); + log(`\tRejected filter count: ${details.rejectedFilterCount}`); + log(`Output rule count: ${rules.length}`); const good = rules.filter(rule => isGood(rule) && isRegex(rule) === false); log(`\tGood: ${good.length}`); @@ -227,11 +231,19 @@ async function main() { log(`Total regex rules count: ${maybeGoodTotalCount}`); // Patch manifest - const manifest = await fs.readFile(`${outputDir}/manifest.json`, { encoding: 'utf8' }) - .then(text => JSON.parse(text)); + const manifest = await fs.readFile( + `${outputDir}/manifest.json`, + { encoding: 'utf8' } + ).then(text => + JSON.parse(text) + ); manifest.declarative_net_request = { rule_resources: ruleResources }; const now = new Date(); - manifest.version = `0.1.${now.getUTCFullYear() - 2000}.${now.getUTCMonth() * 100 + now.getUTCDate()}`; + const yearPart = now.getUTCFullYear() - 2000; + const monthPart = (now.getUTCMonth() + 1) * 1000; + const dayPart = now.getUTCDate() * 10; + const hourPart = Math.floor(now.getUTCHours() / 3) + 1; + manifest.version = manifest.version + `.${yearPart}.${monthPart + dayPart + hourPart}`; await fs.writeFile( `${outputDir}/manifest.json`, JSON.stringify(manifest, null, 2) + '\n' diff --git a/src/js/messaging.js b/src/js/messaging.js index ad8752dfd..f6994d69d 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -160,7 +160,7 @@ const onMessage = function(request, sender, callback) { env: vAPI.webextFlavor.env, }; const t0 = Date.now(); - dnrRulesetFromRawLists(listPromises, options).then(ruleset => { + dnrRulesetFromRawLists(listPromises, options).then(details => { const replacer = (k, v) => { if ( k.startsWith('__') ) { return; } if ( Array.isArray(v) ) { @@ -192,9 +192,14 @@ const onMessage = function(request, sender, callback) { rule.action.type === 'redirect' && rule.action.redirect.transform !== undefined; const runtime = Date.now() - t0; + const { ruleset } = details; const out = [ `dnrRulesetFromRawLists(${JSON.stringify(listNames, null, 2)})`, `Run time: ${runtime} ms`, + `Filters count: ${details.filterCount}`, + `Accepted filter count: ${details.acceptedFilterCount}`, + `Rejected filter count: ${details.rejectedFilterCount}`, + `Resulting DNR rule count: ${ruleset.length}`, ]; const good = ruleset.filter(rule => isUnsupported(rule) === false && diff --git a/src/js/static-dnr-filtering.js b/src/js/static-dnr-filtering.js index 2a1a21259..6be2657ef 100644 --- a/src/js/static-dnr-filtering.js +++ b/src/js/static-dnr-filtering.js @@ -46,7 +46,6 @@ function addToDNR(context, list) { const compiler = staticNetFilteringEngine.createCompiler(parser); writer.properties.set('name', list.name); - parser.setMaxTokenLength(staticNetFilteringEngine.MAX_TOKEN_LENGTH); compiler.start(writer); while ( lineIter.eot() === false ) { @@ -95,8 +94,7 @@ async function dnrRulesetFromRawLists(lists, options = {}) { toLoad.push(list.then(list => toDNR(context, list))); } await Promise.all(toLoad); - const ruleset = staticNetFilteringEngine.dnrFromCompiled('end', context); - return ruleset; + return staticNetFilteringEngine.dnrFromCompiled('end', context); } /******************************************************************************/ diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 331b35975..1a97ffd99 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -3852,6 +3852,9 @@ FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) { good: new Set(), bad: new Set(), invalid: new Set(), + filterCount: 0, + acceptedFilterCount: 0, + rejectedFilterCount: 0, }; } @@ -3859,6 +3862,7 @@ FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) { const reader = args[0]; reader.select('NETWORK_FILTERS:GOOD'); while ( reader.next() ) { + context.filterCount += 1; if ( context.good.has(reader.line) === false ) { context.good.add(reader.line); } @@ -3878,8 +3882,10 @@ FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) { for ( const line of good ) { if ( bad.has(line) ) { + context.rejectedFilterCount += 1; continue; } + context.acceptedFilterCount += 1; const args = unserialize(line); const bits = args[0]; @@ -4201,7 +4207,12 @@ FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) { } } - return Array.from(rulesetMap.values()); + return { + ruleset: Array.from(rulesetMap.values()), + filterCount: context.filterCount, + acceptedFilterCount: context.acceptedFilterCount, + rejectedFilterCount: context.rejectedFilterCount, + }; }; /******************************************************************************/ diff --git a/tools/make-mv3.sh b/tools/make-mv3.sh index c29afebbb..c584bf200 100755 --- a/tools/make-mv3.sh +++ b/tools/make-mv3.sh @@ -7,36 +7,61 @@ set -e echo "*** uBlock0.mv3: Creating extension" DES="dist/build/uBlock0.mv3" -rm -rf $DES + +if [ "$1" != "quick" ]; then + rm -rf $DES +fi + mkdir -p $DES cd $DES DES=$(pwd) cd - > /dev/null -TMPDIR=$(mktemp -d) -mkdir -p $TMPDIR -echo "*** uBlock0.mv3: Copying mv3-specific files" -cp -R platform/mv3/extension/* $DES/ +mkdir -p $DES/css/fonts +mkdir -p $DES/js +mkdir -p $DES/img echo "*** uBlock0.mv3: Copying common files" +cp -R src/css/fonts/* $DES/css/fonts/ +cp src/css/themes/default.css $DES/css/ +cp src/css/common.css $DES/css/ +cp src/css/fa-icons.css $DES/css/ +cp src/js/fa-icons.js $DES/js/ + cp LICENSE.txt $DES/ -echo "*** uBlock0.mv3: Generating rulesets" -./tools/make-nodejs.sh $TMPDIR -cp platform/mv3/package.json $TMPDIR/ -cp platform/mv3/*.js $TMPDIR/ -cd $TMPDIR -node --no-warnings make-rulesets.js output=$DES -cd - > /dev/null -rm -rf $TMPDIR +echo "*** uBlock0.mv3: Copying mv3-specific files" +cp platform/mv3/extension/*.html $DES/ +cp platform/mv3/extension/css/* $DES/css/ +cp platform/mv3/extension/js/* $DES/js/ +cp platform/mv3/extension/img/* $DES/img/ + +if [ "$1" != "quick" ]; then + echo "*** uBlock0.mv3: Generating rulesets" + TMPDIR=$(mktemp -d) + mkdir -p $TMPDIR + cp platform/mv3/extension/manifest.json $DES/ + ./tools/make-nodejs.sh $TMPDIR + cp platform/mv3/package.json $TMPDIR/ + cp platform/mv3/*.js $TMPDIR/ + cd $TMPDIR + node --no-warnings make-rulesets.js output=$DES quick=$QUICK + cd - > /dev/null + rm -rf $TMPDIR +fi echo "*** uBlock0.mv3: extension ready" echo "Extension location: $DES/" -if [ "$1" = all ]; then - echo "*** uBlock0.mv3: Creating webstore package..." - pushd $(dirname $DES/) > /dev/null - zip uBlock0.mv3.zip -qr $(basename $DES/)/* - echo "Package location: $(pwd)/uBlock0.mv3.zip" - popd > /dev/null -fi +echo "*** uBlock0.mv3: Creating webstore package..." +PACKAGENAME=uBlock0_$(jq -r .version $DES/manifest.json).mv3.zip +TMPDIR=$(mktemp -d) +mkdir -p $TMPDIR +cp -R $DES/* $TMPDIR/ +cd $TMPDIR > /dev/null +rm log.txt +zip $PACKAGENAME -qr ./* +cp $PACKAGENAME $(dirname $DES/)/ +cd - > /dev/null +rm -rf $TMPDIR +echo "Package location: $(pwd)/$PACKAGENAME"