mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-22 02:12:44 +01:00
Add per-site on/off switch to mv3 experimental version
This commit is contained in:
parent
e420b75b91
commit
224410a6f5
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@ -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
|
||||
|
9
Makefile
9
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:
|
||||
|
@ -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}`);
|
||||
});
|
||||
})();
|
227
platform/mv3/extension/css/popup.css
Normal file
227
platform/mv3/extension/css/popup.css
Normal file
@ -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); */
|
||||
}
|
188
platform/mv3/extension/js/background.js
Normal file
188
platform/mv3/extension/js/background.js
Normal file
@ -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;
|
||||
}
|
||||
});
|
||||
})();
|
114
platform/mv3/extension/js/popup.js
Normal file
114
platform/mv3/extension/js/popup.js
Normal file
@ -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();
|
||||
|
||||
/******************************************************************************/
|
@ -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"
|
||||
}
|
||||
|
57
platform/mv3/extension/popup.html
Normal file
57
platform/mv3/extension/popup.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html id="uBO-popup-panel" class="desktop">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="css/default.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/fa-icons.css">
|
||||
<link rel="stylesheet" href="css/popup.css">
|
||||
<title data-i18n="extName"></title>
|
||||
</head>
|
||||
|
||||
<body class="loading">
|
||||
<div id="main">
|
||||
<div id="sticky">
|
||||
<div id="stickyTools">
|
||||
<div class="rulesetTools">
|
||||
<span id="saveRules" class="fa-icon" data-i18n-title="popupTipSaveRules">lock</span>
|
||||
<span id="revertRules" class="fa-icon" data-i18n-title="popupTipRevertRules">eraser</span>
|
||||
</div>
|
||||
<div id="switch" role="button" aria-label tabindex="0" title>
|
||||
<span class="fa-icon"><!--
|
||||
Power button taken from Font Awesome v4.7.0 by Dave Gandy.
|
||||
Unlike other FA icons, the power button is inlined here so
|
||||
that we can use a clip-path in order to ensure that the stroke
|
||||
does not "bleed" outside the fill area.
|
||||
--><svg viewBox="0 0 1536 1664">
|
||||
<defs>
|
||||
<path id="power-off-path" d="m 1536,896 q 0,156 -61,298 -61,142 -164,245 -103,103 -245,164 -142,61 -298,61 -156,0 -298,-61 Q 328,1542 225,1439 122,1336 61,1194 0,1052 0,896 0,714 80.5,553 161,392 307,283 q 43,-32 95.5,-25 52.5,7 83.5,50 32,42 24.5,94.5 Q 503,455 461,487 363,561 309.5,668 256,775 256,896 q 0,104 40.5,198.5 40.5,94.5 109.5,163.5 69,69 163.5,109.5 94.5,40.5 198.5,40.5 104,0 198.5,-40.5 Q 1061,1327 1130,1258 1199,1189 1239.5,1094.5 1280,1000 1280,896 1280,775 1226.5,668 1173,561 1075,487 1033,455 1025.5,402.5 1018,350 1050,308 q 31,-43 84,-50 53,-7 95,25 146,109 226.5,270 80.5,161 80.5,343 z m -640,-768 0,640 q 0,52 -38,90 -38,38 -90,38 -52,0 -90,-38 -38,-38 -38,-90 l 0,-640 q 0,-52 38,-90 38,-38 90,-38 52,0 90,38 38,38 38,90 z"/>
|
||||
<clipPath id="power-off-clip"><use href="#power-off-path"/></clipPath>
|
||||
</defs>
|
||||
<use href="#power-off-path" clip-path="url(#power-off-clip)"/>
|
||||
</svg><!--
|
||||
--></span>
|
||||
</div>
|
||||
<div class="rulesetTools">
|
||||
<span id="refresh" class="fa-icon">refresh</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hostname"><span></span>­<span></span></div>
|
||||
</div>
|
||||
<div id="basicTools" class="toolRibbon">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<a href="dashboard.html" class="fa-icon tool" target="uBODashboard" tabindex="0" data-i18n-title="popupTipDashboard">cogs<span class="caption" data-i18n="popupTipDashboard"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/fa-icons.js"></script>
|
||||
<script src="js/popup.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -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'
|
||||
|
@ -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 &&
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user