[mv3] Add support for specific cosmetic filtering

Specific plain CSS cosmetic filters are now supported.

Cosmetic filtering will occur only after the user explicitly
grant uBO extended permissions for a given site, so that it
can inject CSS on the site.

A new button in the popup panel allows a user to grant/revoke
extended permissions to/from uBO Lite for the current site.

More capabilities will be carefully added for when extended
permissions are granted on a site, so specific cosmetic
filtering through plain CSS is the first implemented capability.

Generic and procedural cosmetic filtering is not implemented.

The current implementation for plain CSS cosmetic filters is
through declarative content injection, which does not require
the service worker to be alive, the browser takes care to
inject the cosmetic filters.

However declarative CSS injection does not support user
styles, so the injected cosmetic filters are "weak". I consider
this is a browser issue, since user styles are supported by
Chromium, there is just no way in the API to specify user
styles for the injected content.

Also:
- Fixed dark theme issues
- Added Steven Black's hosts file

Keep in mind all this is very experimental and implementation
details in this release may (will) greatly change in the future.
This commit is contained in:
Raymond Hill 2022-09-15 13:14:08 -04:00
parent b343cdc374
commit 34aab95107
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
17 changed files with 797 additions and 287 deletions

View File

@ -26,7 +26,7 @@
</div>
<div id="templates" style="display: none;">
<div id="templates">
<div class="groupEntry">
<div class="geDetails"><span class="geName"></span> <span class="geCount"></span></div>
<div class="listEntries"></div>

View File

@ -7,6 +7,7 @@ body {
}
#actions {
background-color: var(--surface-1);
padding: 0.5em 0;
position: sticky;
top: 0;
z-index: 10;
@ -184,3 +185,7 @@ body.updating .listEntry.checked.obsolete .updating {
:root.mobile .li.listEntry .iconbar {
margin-top: 0.2em;
}
#templates {
display: none;
}

View File

@ -120,13 +120,13 @@ body.needSave #revertRules {
#rulesetStats {
padding: 0 var(--popup-gap-thin);
}
#rulesetStats > h1 {
#rulesetStats .rulesetDetails h1 {
font-size: 1em;
margin-bottom: var(--popup-gap-thin);
margin: 0.5em 0;
}
#rulesetStats > p {
#rulesetStats .rulesetDetails p {
font-size: var(--font-size-smaller);
margin: var(--popup-gap-thin) 0 var(--popup-gap) var(--popup-gap-thin);
margin: 0.5em 0 0.5em var(--popup-gap-thin);
}
.itemRibbon {
@ -147,9 +147,8 @@ body.needSave #revertRules {
display: grid;
grid-auto-columns: 1fr;
grid-auto-flow: column;
grid-template: auto / repeat(4, 1fr);
grid-template: auto / repeat(5, 1fr);
justify-items: center;
margin: 0;
white-space: normal;
}
.toolRibbon .tool {
@ -178,9 +177,20 @@ body.needSave #revertRules {
body.mobile.no-tooltips .toolRibbon .tool {
font-size: 1.6em;
}
.toolRibbon.genericTools {
margin-bottom: 0;
}
#basicTools {
margin-top: var(--default-gap);
body:not(.hasGreatPowers) [data-i18n-title="popupGrantGreatPowers"],
body.hasGreatPowers [data-i18n-title="popupRevokeGreatPowers"] {
display: flex;
}
body:not(.hasGreatPowers) [data-i18n-title="popupRevokeGreatPowers"],
body.hasGreatPowers [data-i18n-title="popupGrantGreatPowers"] {
display: none;
}
body.hasGreatPowers [data-i18n-title="popupRevokeGreatPowers"] {
fill: var(--popup-power-ink);
}
#moreOrLess {
@ -204,7 +214,7 @@ body.mobile.no-tooltips .toolRibbon .tool {
border-inline-start: 1px solid var(--surface-1);
text-align: end;
}
body[data-section="a"] #moreButton {
body[data-section="a b"] #moreButton {
pointer-events: none;
visibility: hidden;
}
@ -215,6 +225,9 @@ body[data-section=""] #lessButton {
body:not([data-section~="a"]) [data-section="a"] {
display: none;
}
body:not([data-section~="b"]) [data-section="b"] {
display: none;
}
/* configurable UI elements */
:root:not(.mobile) .toolRibbon .caption,
@ -274,3 +287,7 @@ body:not([data-section~="a"]) [data-section="a"] {
background-color: var(--surface-2);
/* background-color: var(--popup-toolbar-surface-hover); */
}
#templates {
display: none;
}

View File

@ -55,28 +55,26 @@ const renderFilterLists = function(soft) {
}
const on = enabledRulesets.includes(ruleset.id);
li.classList.toggle('checked', on);
let elem;
if ( dom.attr(li, 'data-listkey') !== ruleset.id ) {
dom.attr(li, 'data-listkey', ruleset.id);
qs$('input[type="checkbox"]', li).checked = on;
qs$('.listname', li).textContent = ruleset.name || ruleset.id;
dom.removeClass(li, 'toRemove');
if ( ruleset.supportName ) {
dom.addClass(li, 'support');
elem = qs$('a.support', li);
dom.attr(elem, 'href', ruleset.supportURL);
dom.attr(elem, 'title', ruleset.supportName);
dom.cl.remove(li, 'toRemove');
if ( ruleset.homeURL ) {
dom.cl.add(li, 'support');
const elem = qs$('a.support', li);
dom.attr(elem, 'href', ruleset.homeURL);
} else {
dom.removeClass(li, 'support');
dom.cl.remove(li, 'support');
}
if ( ruleset.instructionURL ) {
dom.addClass(li, 'mustread');
dom.cl.add(li, 'mustread');
dom.attr(qs$('a.mustread', li), 'href', ruleset.instructionURL);
} else {
dom.removeClass(li, 'mustread');
dom.cl.remove(li, 'mustread');
}
dom.toggleClass(li, 'isDefault', ruleset.isDefault === true);
dom.toggleClass(li, 'unused', hideUnused && !on);
dom.cl.toggle(li, 'isDefault', ruleset.isDefault === true);
dom.cl.toggle(li, 'unused', hideUnused && !on);
}
// https://github.com/gorhill/uBlock/issues/1429
if ( !soft ) {
@ -141,7 +139,7 @@ const renderFilterLists = function(soft) {
// Incremental rendering: this will allow us to easily discard unused
// DOM list entries.
dom.addClass(
dom.cl.add(
qsa$('#lists .listEntries .listEntry[data-listkey]'),
'discard'
);
@ -169,7 +167,7 @@ const renderFilterLists = function(soft) {
],
]);
dom.toggleClass(dom.body, 'hideUnused', mustHideUnusedLists('*'));
dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*'));
for ( const [ groupKey, groupRulesets ] of groups ) {
let liGroup = liFromListGroup(groupKey, groupRulesets);
@ -193,7 +191,7 @@ const renderFilterLists = function(soft) {
/******************************************************************************/
const renderWidgets = function() {
dom.toggleClass(
dom.cl.toggle(
qs$('#buttonApply'),
'disabled',
filteringSettingsHash === hashFromCurrentFromSettings()
@ -231,12 +229,16 @@ const hashFromCurrentFromSettings = function() {
return hash.join();
};
self.hasUnsavedData = function() {
return hashFromCurrentFromSettings() !== filteringSettingsHash;
};
/******************************************************************************/
function onListsetChanged(ev) {
const input = ev.target;
const li = input.closest('.listEntry');
dom.toggleClass(li, 'checked', input.checked);
dom.cl.toggle(li, 'checked', input.checked);
renderWidgets();
}
@ -265,7 +267,7 @@ const applyEnabledRulesets = async function() {
};
const buttonApplyHandler = async function() {
dom.removeClass(qs$('#buttonApply'), 'enabled');
dom.cl.remove(qs$('#buttonApply'), 'enabled');
await applyEnabledRulesets();
renderWidgets();
};
@ -298,7 +300,7 @@ const toggleHideUnusedLists = function(which) {
hideUnusedSet.add(which);
}
document.body.classList.toggle('hideUnused', mustHide);
dom.toggleClass(qsa$('.groupEntry[data-groupkey]'), 'hideUnused', mustHide);
dom.cl.toggle(qsa$('.groupEntry[data-groupkey]'), 'hideUnused', mustHide);
} else {
const doesHide = hideUnusedSet.has(which);
if ( doesHide ) {
@ -308,11 +310,11 @@ const toggleHideUnusedLists = function(which) {
}
mustHide = doesHide === doesHideAll;
groupSelector = `.groupEntry[data-groupkey="${which}"]`;
dom.toggleClass(qsa$(groupSelector), 'hideUnused', mustHide);
dom.cl.toggle(qsa$(groupSelector), 'hideUnused', mustHide);
}
for ( const elem of qsa$(`#lists ${groupSelector} .listEntry[data-listkey] input[type="checkbox"]:not(:checked)`) ) {
dom.toggleClass(
dom.cl.toggle(
elem.closest('.listEntry[data-listkey]'),
'unused',
mustHide
@ -345,24 +347,6 @@ simpleStorage.getItem('hideUnusedFilterLists').then(value => {
/******************************************************************************/
self.hasUnsavedData = function() {
return hashFromCurrentFromSettings() !== filteringSettingsHash;
};
/******************************************************************************/
dom.on(
qs$('#lists'),
'click',
'.listEntry label *',
ev => {
if ( ev.target.matches('input,.forinput') ) { return; }
ev.preventDefault();
}
);
/******************************************************************************/
sendMessage({
what: 'getRulesetData',
}).then(data => {

View File

@ -0,0 +1,156 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2022-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
*/
/* jshint esversion:11 */
'use strict';
/******************************************************************************/
import { browser, dnr } from './ext.js';
import { fetchJSON } from './fetch.js';
/******************************************************************************/
const matchesFromHostnames = hostnames => {
const out = [];
for ( const hn of hostnames ) {
if ( hn === '*' ) {
out.push('*://*/*');
} else {
out.push(`*://*.${hn}/*`);
}
}
return out;
};
const hostnamesFromMatches = origins => {
const out = [];
for ( const origin of origins ) {
const match = /^\*:\/\/([^\/]+)\/\*/.exec(origin);
if ( match === null ) { continue; }
out.push(match[1]);
}
return out;
};
/******************************************************************************/
const toRegisterable = entry => {
const directive = {
id: entry.css,
allFrames: true,
css: [
`/content-css/${entry.rulesetId}/${entry.css.slice(0,1)}/${entry.css.slice(1,8)}.css`
],
};
if ( entry.matches ) {
directive.matches = matchesFromHostnames(entry.matches);
} else {
directive.matches = [ '*://*/*' ];
}
if ( entry.excludeMatches ) {
directive.excludeMatches = matchesFromHostnames(entry.excludeMatches);
}
return directive;
};
/******************************************************************************/
async function registerCSS() {
const [
origins,
rulesetIds,
registered,
cssDetails,
] = await Promise.all([
browser.permissions.getAll(),
dnr.getEnabledRulesets(),
browser.scripting.getRegisteredContentScripts(),
fetchJSON('/content-css/css-specific'),
]).then(results => {
results[0] = new Set(hostnamesFromMatches(results[0].origins));
results[3] = new Map(results[3]);
return results;
});
if ( origins.has('*') && origins.size > 1 ) {
origins.clear();
origins.add('*');
}
const toRegister = new Map();
for ( const rulesetId of rulesetIds ) {
const cssEntries = cssDetails.get(rulesetId);
if ( cssEntries === undefined ) { continue; }
for ( const entry of cssEntries ) {
entry.rulesetId = rulesetId;
for ( const origin of origins ) {
if ( origin === '*' || Array.isArray(entry.matches) === false ) {
toRegister.set(entry.css, entry);
continue;
}
let hn = origin;
for (;;) {
if ( entry.matches.includes(hn) ) {
toRegister.set(entry.css, entry);
break;
}
if ( hn === '*' ) { break; }
const pos = hn.indexOf('.');
hn = pos !== -1
? hn.slice(pos+1)
: '*';
}
}
}
}
const before = new Set(registered.map(entry => entry.id));
const toAdd = [];
for ( const [ id, entry ] of toRegister ) {
if ( before.has(id) ) { continue; }
toAdd.push(toRegisterable(entry));
}
const toRemove = [];
for ( const id of before ) {
if ( toRegister.has(id) ) { continue; }
toRemove.push(id);
}
const todo = [];
if ( toRemove.length !== 0 ) {
todo.push(browser.scripting.unregisterContentScripts(toRemove));
console.info(`Unregistered ${toRemove.length} CSS content scripts`);
}
if ( toAdd.length !== 0 ) {
todo.push(browser.scripting.registerContentScripts(toAdd));
console.info(`Registered ${toAdd.length} CSS content scripts`);
}
if ( todo.length === 0 ) { return; }
return Promise.all(todo);
}
/******************************************************************************/
export { registerCSS };

View File

@ -25,7 +25,9 @@
/******************************************************************************/
import { dnr, i18n, runtime } from './ext.js';
import { browser, dnr, i18n, runtime } from './ext.js';
import { fetchJSON } from './fetch.js';
import { registerCSS } from './background-css.js';
/******************************************************************************/
@ -96,16 +98,6 @@ async function saveRulesetConfig() {
/******************************************************************************/
function fetchJSON(filename) {
return fetch(`/rulesets/${filename}.json`).then(response =>
response.json()
).catch(reason => {
console.info(reason);
});
}
/******************************************************************************/
async function updateRegexRules(dynamicRules) {
// Avoid testing already tested regexes
const validRegexSet = new Set(
@ -122,7 +114,8 @@ async function updateRegexRules(dynamicRules) {
const toFetch = [];
for ( const details of rulesetDetails.values() ) {
if ( details.enabled !== true ) { continue; }
toFetch.push(fetchJSON(`${details.id}.regexes`));
if ( details.rules.regexes === 0 ) { continue; }
toFetch.push(fetchJSON(`/rulesets/${details.id}.regexes`));
}
const regexRulesets = await Promise.all(toFetch);
@ -303,11 +296,7 @@ async function getEnabledRulesetsStats() {
for ( const id of ids ) {
const ruleset = rulesetDetails.get(id);
if ( ruleset === undefined ) { continue; }
out.push({
name: ruleset.name,
filterCount: ruleset.filters.accepted,
ruleCount: ruleset.rules.accepted,
});
out.push(ruleset);
}
return out;
}
@ -344,6 +333,99 @@ async function defaultRulesetsFromLanguage() {
/******************************************************************************/
async function hasGreatPowers(origin) {
return browser.permissions.contains({
origins: [ `${origin}/*` ]
});
}
function grantGreatPowers(hostname) {
return browser.permissions.request({
origins: [
`*://${hostname}/*`,
]
});
}
function revokeGreatPowers(hostname) {
return browser.permissions.remove({
origins: [
`*://${hostname}/*`,
]
});
}
/******************************************************************************/
function onMessage(request, sender, callback) {
switch ( request.what ) {
case 'applyRulesets': {
enableRulesets(request.enabledRulesets).then(( ) => {
rulesetConfig.enabledRulesets = request.enabledRulesets;
return saveRulesetConfig();
}).then(( ) => {
callback();
});
return true;
}
case 'getRulesetData': {
dnr.getEnabledRulesets().then(enabledRulesets => {
callback({
enabledRulesets,
rulesetDetails: Array.from(rulesetDetails.values()),
});
});
return true;
}
case 'grantGreatPowers':
grantGreatPowers(request.hostname).then(granted => {
callback(granted);
});
return true;
case 'popupPanelData': {
Promise.all([
matchesTrustedSiteDirective(request),
hasGreatPowers(request.origin),
getEnabledRulesetsStats(),
]).then(results => {
callback({
isTrusted: results[0],
hasGreatPowers: results[1],
rulesetDetails: results[2],
});
});
return true;
}
case 'revokeGreatPowers':
revokeGreatPowers(request.hostname).then(removed => {
callback(removed);
});
return true;
case 'toggleTrustedSiteDirective': {
toggleTrustedSiteDirective(request).then(response => {
callback(response);
});
return true;
}
default:
break;
}
}
async function onPermissionsChanged() {
await registerCSS();
}
/******************************************************************************/
async function start() {
// Fetch enabled rulesets and dynamic rules
const dynamicRules = await dnr.getDynamicRules();
@ -352,7 +434,7 @@ async function start() {
}
// Fetch ruleset details
await fetchJSON('ruleset-details').then(entries => {
await fetchJSON('/rulesets/ruleset-details').then(entries => {
if ( entries === undefined ) { return; }
for ( const entry of entries ) {
rulesetDetails.set(entry.id, entry);
@ -386,61 +468,11 @@ async function start() {
dnr.setExtensionActionOptions({ displayActionCountAsBadgeText: true });
}
/******************************************************************************/
function messageListener(request, sender, callback) {
switch ( request.what ) {
case 'getRulesetData': {
dnr.getEnabledRulesets().then(enabledRulesets => {
callback({
enabledRulesets,
rulesetDetails: Array.from(rulesetDetails.values()),
});
});
return true;
}
case 'applyRulesets': {
enableRulesets(request.enabledRulesets).then(( ) => {
rulesetConfig.enabledRulesets = request.enabledRulesets;
return saveRulesetConfig();
}).then(( ) => {
callback();
});
return true;
}
case 'popupPanelData': {
Promise.all([
matchesTrustedSiteDirective(request),
getEnabledRulesetsStats(),
]).then(results => {
callback({
isTrusted: results[0],
rulesetDetails: results[1],
});
});
return true;
}
case 'toggleTrustedSiteDirective': {
toggleTrustedSiteDirective(request).then(response => {
callback(response);
});
return true;
}
default:
break;
}
}
/******************************************************************************/
(async ( ) => {
await start();
runtime.onMessage.addListener(messageListener);
runtime.onMessage.addListener(onMessage);
browser.permissions.onAdded.addListener(onPermissionsChanged);
browser.permissions.onRemoved.addListener(onPermissionsChanged);
})();

View File

@ -25,15 +25,15 @@
/******************************************************************************/
function normalizeTarget(target) {
const normalizeTarget = target => {
if ( target === null ) { return []; }
if ( Array.isArray(target) ) { return target; }
return target instanceof Element
? [ target ]
: Array.from(target);
}
};
function makeEventHandler(selector, callback) {
const makeEventHandler = (selector, callback) => {
return function(event) {
const dispatcher = event.currentTarget;
if (
@ -52,30 +52,11 @@ function makeEventHandler(selector, callback) {
callback.call(receiver, event);
}
};
}
};
/******************************************************************************/
class dom {
static addClass(target, cl) {
for ( const elem of normalizeTarget(target) ) {
elem.classList.add(cl);
}
}
static toggleClass(target, cl, state = undefined) {
for ( const elem of normalizeTarget(target) ) {
elem.classList.toggle(cl, state);
}
}
static removeClass(target, cl) {
for ( const elem of normalizeTarget(target) ) {
elem.classList.remove(cl);
}
}
static attr(target, attr, value = undefined) {
for ( const elem of normalizeTarget(target) ) {
if ( value === undefined ) {
@ -85,6 +66,12 @@ class dom {
}
}
static text(target, text) {
for ( const elem of normalizeTarget(target) ) {
elem.textContent = text;
}
}
static remove(target) {
for ( const elem of normalizeTarget(target) ) {
elem.remove();
@ -104,6 +91,37 @@ class dom {
}
}
dom.cl = class {
static add(target, name) {
for ( const elem of normalizeTarget(target) ) {
elem.classList.add(name);
}
}
static remove(target, name) {
for ( const elem of normalizeTarget(target) ) {
elem.classList.remove(name);
}
}
static toggle(target, name, state) {
for ( const elem of normalizeTarget(target) ) {
elem.classList.toggle(name, state);
}
}
static has(target, name) {
for ( const elem of normalizeTarget(target) ) {
if ( elem.classList.contains(name) ) {
return true;
}
}
return false;
}
};
dom.html = document.documentElement;
dom.head = document.head;
dom.body = document.body;
/******************************************************************************/
@ -118,4 +136,15 @@ function qsa$(s, elem = undefined) {
/******************************************************************************/
{
const mql = self.matchMedia('(prefers-color-scheme: dark)');
const theme = mql instanceof Object && mql.matches === true
? 'dark'
: 'light';
dom.cl.toggle(dom.html, 'dark', theme === 'dark');
dom.cl.toggle(dom.html, 'light', theme !== 'dark');
}
/******************************************************************************/
export { dom, qs$, qsa$ };

View File

@ -0,0 +1,38 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2022-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
*/
/* jshint esversion:11 */
'use strict';
/******************************************************************************/
function fetchJSON(path) {
return fetch(`${path}.json`).then(response =>
response.json()
).catch(reason => {
console.info(reason);
});
}
/******************************************************************************/
export { fetchJSON };

View File

@ -26,13 +26,35 @@
/******************************************************************************/
import { browser, sendMessage } from './ext.js';
import { dom, qs$ } from './dom.js';
import { i18n$ } from './i18n.js';
import { simpleStorage } from './storage.js';
/******************************************************************************/
let currentTab = {};
let originalTrustedState = false;
let tabHostname = '';
/******************************************************************************/
let originalStateHash = '';
function getCurrentStateHash() {
const parts = [
dom.cl.has(dom.body, 'off'),
dom.cl.has(dom.body, 'hasGreatPowers'),
];
return parts.join('\t');
}
function onStateHashChanged() {
dom.cl.toggle(
dom.body,
'needReload',
getCurrentStateHash() !== originalStateHash
);
}
/******************************************************************************/
@ -44,7 +66,9 @@ async function toggleTrustedSiteDirective() {
return;
}
if ( url instanceof URL === false ) { return; }
const targetTrustedState = document.body.classList.contains('off');
const targetTrustedState = dom.cl.has(dom.body, 'off');
const newTrustedState = await sendMessage({
what: 'toggleTrustedSiteDirective',
origin: url.origin,
@ -53,101 +77,37 @@ async function toggleTrustedSiteDirective() {
}).catch(( ) =>
targetTrustedState === false
);
document.body.classList.toggle('off', newTrustedState === true);
document.body.classList.toggle(
'needReload',
newTrustedState !== originalTrustedState
);
dom.cl.toggle(dom.body, 'off', newTrustedState === true);
onStateHashChanged();
}
dom.on(qs$('#switch'), 'click', toggleTrustedSiteDirective);
/******************************************************************************/
function reloadTab(ev) {
browser.tabs.reload(currentTab.id, {
bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey,
});
document.body.classList.remove('needReload');
originalTrustedState = document.body.classList.contains('off');
dom.cl.remove(dom.body, 'needReload');
originalStateHash = getCurrentStateHash();
}
/******************************************************************************/
async function init() {
const [ tab ] = await browser.tabs.query({ active: true });
if ( tab instanceof Object === false ) { return true; }
currentTab = tab;
let url;
try {
url = new URL(currentTab.url);
} catch(ex) {
}
let popupPanelData;
if ( url !== undefined ) {
popupPanelData = await sendMessage({
what: 'popupPanelData',
origin: url.origin,
});
originalTrustedState = popupPanelData.isTrusted === 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
);
if ( popupPanelData ) {
const parent = document.querySelector('#rulesetStats');
for ( const details of popupPanelData.rulesetDetails ) {
const h1 = document.createElement('h1');
h1.textContent = details.name;
parent.append(h1);
const p = document.createElement('p');
p.textContent = i18n$('perRulesetStats')
.replace('{{ruleCount}}', details.ruleCount.toLocaleString())
.replace('{{filterCount}}', details.filterCount.toLocaleString());
parent.append(p);
}
}
document.body.classList.remove('loading');
return true;
}
async function tryInit() {
try {
await init();
} catch(ex) {
setTimeout(tryInit, 100);
}
}
tryInit();
dom.on(qs$('#refresh'), 'click', reloadTab);
/******************************************************************************/
// The popup panel is made of sections. Visibility of sections can be
// toggled on/off.
const maxNumberOfSections = 1;
const maxNumberOfSections = 2;
const sectionBitsFromAttribute = function() {
const attr = document.body.dataset.section;
if ( attr === '' ) { return 0; }
const value = dom.body.dataset.section;
if ( value === '' ) { return 0; }
let bits = 0;
for ( const c of attr.split(' ') ) {
for ( const c of value.split(' ') ) {
bits |= 1 << (c.charCodeAt(0) - 97);
}
return bits;
@ -156,13 +116,13 @@ const sectionBitsFromAttribute = function() {
const sectionBitsToAttribute = function(bits) {
if ( typeof bits !== 'number' ) { return; }
if ( isNaN(bits) ) { return; }
const attr = [];
const value = [];
for ( let i = 0; i < maxNumberOfSections; i++ ) {
const bit = 1 << i;
if ( (bits & bit) === 0 ) { continue; }
attr.push(String.fromCharCode(97 + i));
value.push(String.fromCharCode(97 + i));
}
document.body.dataset.section = attr.join(' ');
dom.body.dataset.section = value.join(' ');
};
async function toggleSections(more) {
@ -186,13 +146,111 @@ simpleStorage.getItem('popupPanelSections').then(s => {
sectionBitsToAttribute(parseInt(s, 10) || 0);
});
document.querySelector('#moreButton').addEventListener('click', ( ) => {
dom.on(qs$('#moreButton'), 'click', ( ) => {
toggleSections(true);
});
document.querySelector('#lessButton').addEventListener('click', ( ) => {
dom.on(qs$('#lessButton'), 'click', ( ) => {
toggleSections(false);
});
/******************************************************************************/
async function grantGreatPowers() {
const granted = await sendMessage({
what: 'grantGreatPowers',
hostname: tabHostname,
});
if ( granted !== true ) { return; }
dom.cl.add(dom.body, 'hasGreatPowers');
onStateHashChanged();
}
async function revokeGreatPowers() {
const removed = await sendMessage({
what: 'revokeGreatPowers',
hostname: tabHostname,
});
if ( removed !== true ) { return; }
dom.cl.remove(dom.body, 'hasGreatPowers');
onStateHashChanged();
}
dom.on(qs$('#toggleGreatPowers'), 'click', ( ) => {
if ( dom.cl.has(dom.body, 'hasGreatPowers' ) ) {
revokeGreatPowers();
} else {
grantGreatPowers();
}
});
/******************************************************************************/
async function init() {
const [ tab ] = await browser.tabs.query({ active: true });
if ( tab instanceof Object === false ) { return true; }
currentTab = tab;
let url;
try {
url = new URL(currentTab.url);
tabHostname = url.hostname || '';
} catch(ex) {
}
let popupPanelData = {};
if ( url !== undefined ) {
popupPanelData = await sendMessage({
what: 'popupPanelData',
origin: url.origin,
});
}
dom.cl.toggle(
dom.body,
'off',
popupPanelData.isTrusted === true
);
dom.cl.toggle(
dom.body,
'hasGreatPowers',
popupPanelData.hasGreatPowers === true
);
dom.text(qs$('#hostname'), tabHostname);
const parent = qs$('#rulesetStats');
for ( const details of popupPanelData.rulesetDetails || [] ) {
const div = qs$('#templates .rulesetDetails').cloneNode(true);
dom.text(qs$('h1', div), details.name);
const { rules, filters, css } = details;
dom.text(
qs$('p', div),
i18n$('perRulesetStats')
.replace('{{ruleCount}}', rules.accepted.toLocaleString())
.replace('{{filterCount}}', filters.accepted.toLocaleString())
.replace('{{cssSpecificCount}}', css.specific.toLocaleString())
);
parent.append(div);
}
dom.cl.remove(dom.body, 'loading');
originalStateHash = getCurrentStateHash();
return true;
}
async function tryInit() {
try {
await init();
} catch(ex) {
setTimeout(tryInit, 100);
}
}
tryInit();
/******************************************************************************/

View File

@ -28,9 +28,13 @@
"minimum_chrome_version": "101.0",
"name": "__MSG_extName__",
"options_page": "dashboard.html",
"optional_host_permissions": [
"<all_urls>"
],
"permissions": [
"activeTab",
"declarativeNetRequest"
"declarativeNetRequest",
"scripting"
],
"short_name": "uBO Lite",
"version": "0.1"

View File

@ -40,18 +40,20 @@
</div>
<div id="hostname"><span></span>&shy;<span></span></div>
</div>
<div id="basicTools" class="toolRibbon" data-more="c">
<span class="fa-icon tool needPick" data-i18n-title="popupTipZapper">bolt<span class="caption"></span></span>
<span class="fa-icon tool needPick" data-i18n-title="popupTipPicker">eye-dropper<span class="caption"></span></span>
<span class="fa-icon tool needPick" data-i18n-title="popupTipReport">comment-alt<span class="caption"></span></span>
<a href="logger-ui.html#_" class="fa-icon tool" target="uBOLogger" tabindex="0" data-i18n-title="popupTipLog">list-alt<span class="caption"></span></a>
<div class="toolRibbon pageTools">
<span id="toggleGreatPowers">
<span class="fa-icon tool enabled" data-i18n-title="popupGrantGreatPowers">sun-o<span class="caption"></span></span>
<span class="fa-icon tool enabled" data-i18n-title="popupRevokeGreatPowers">sun<span class="caption"></span></span>
</span>
<span></span>
<span></span>
<span></span>
<a href="dashboard.html" class="fa-icon tool enabled" target="uBODashboard" tabindex="0" data-i18n-title="popupTipDashboard">cogs<span class="caption" data-i18n="popupTipDashboard"></span></a>
</div>
<hr data-section="a">
<div id="rulesetStats" data-section="a">
</div>
<hr>
<div id="moreOrLess" class="">
<hr data-section="a">
<div id="moreOrLess">
<span id="moreButton">
<span data-i18n="popupMoreButton">_</span>&emsp;<span class="fa-icon">angle-up</span>
</span>
@ -61,6 +63,10 @@
</div>
</div>
<div id="templates">
<div class="rulesetDetails"><h1></h1><p data-section="b"></p></div>
</div>
<script src="js/fa-icons.js"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/popup.js" type="module"></script>

View File

@ -25,7 +25,9 @@
import fs from 'fs/promises';
import https from 'https';
import path from 'path';
import process from 'process';
import { createHash } from 'crypto';
import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js';
import { StaticFilteringParser } from './js/static-filtering-parser.js';
@ -91,21 +93,47 @@ const log = (text, silent = false) => {
/******************************************************************************/
const fetchList = url => {
const urlToFileName = url => {
return url
.replace(/^https?:\/\//, '')
.replace(/\//g, '_')
;
};
const fetchList = (url, cacheDir) => {
return new Promise((resolve, reject) => {
log(`\tFetching ${url}`);
const fname = urlToFileName(url);
fs.readFile(`${cacheDir}/${fname}`, { encoding: 'utf8' }).then(content => {
log(`\tFetched local ${url}`);
resolve({ url, content });
}).catch(( ) => {
log(`\tFetching remote ${url}`);
https.get(url, response => {
const data = [];
response.on('data', chunk => {
data.push(chunk.toString());
});
response.on('end', ( ) => {
resolve({ url, content: data.join('') });
const content = data.join('');
try {
writeFile(`${cacheDir}/${fname}`, content);
} catch (ex) {
}
resolve({ url, content });
});
}).on('error', error => {
reject(error);
});
});
});
};
/******************************************************************************/
const writeFile = async (fname, data) => {
const dir = path.dirname(fname);
await fs.mkdir(dir, { recursive: true });
return fs.writeFile(fname, data);
};
/******************************************************************************/
@ -117,7 +145,7 @@ async function main() {
const writeOps = [];
const ruleResources = [];
const rulesetDetails = [];
const regexRulesetDetails = new Map();
const cssDetails = new Map();
const outputDir = commandLineArgs.get('output') || '.';
// Get manifest content
@ -159,11 +187,8 @@ async function main() {
};
const rulesetDir = `${outputDir}/rulesets`;
const rulesetDirPromise = fs.mkdir(`${rulesetDir}`, { recursive: true });
const writeFile = (path, data) =>
rulesetDirPromise.then(( ) =>
fs.writeFile(path, data));
const cacheDir = `${outputDir}/../mv3-data`;
const cssDir = `${outputDir}/content-css`;
const rulesetFromURLS = async function(assetDetails) {
log('============================');
@ -187,7 +212,7 @@ async function main() {
}
fetchedURLs.add(part.url);
newParts.push(
fetchList(part.url).then(details => {
fetchList(part.url, cacheDir).then(details => {
const { url } = details;
const content = details.content.trim();
if ( typeof content === 'string' && content !== '' ) {
@ -213,11 +238,12 @@ async function main() {
return;
}
const details = await dnrRulesetFromRawLists([ { name: assetDetails.id, text } ], { env });
const { ruleset: rules } = details;
log(`Input filter count: ${details.filterCount}`);
log(`\tAccepted filter count: ${details.acceptedFilterCount}`);
log(`\tRejected filter count: ${details.rejectedFilterCount}`);
const results = await dnrRulesetFromRawLists([ { name: assetDetails.id, text } ], { env });
const { network } = results;
const { ruleset: rules } = network;
log(`Input filter count: ${network.filterCount}`);
log(`\tAccepted filter count: ${network.acceptedFilterCount}`);
log(`\tRejected filter count: ${network.rejectedFilterCount}`);
log(`Output rule count: ${rules.length}`);
const good = rules.filter(rule => isGood(rule) && isRegex(rule) === false);
@ -253,15 +279,51 @@ async function main() {
true
);
writeOps.push(
writeFile(
`${rulesetDir}/${assetDetails.id}.json`,
`${JSON.stringify(good, replacer)}\n`
)
);
if ( regexes.length !== 0 ) {
writeOps.push(
writeFile(
`${rulesetDir}/${assetDetails.id}.regexes.json`,
`${JSON.stringify(regexes, replacer)}\n`
)
);
}
const { cosmetic } = results;
const cssEntries = [];
for ( const entry of cosmetic ) {
const fname = createHash('sha256').update(entry.css).digest('hex').slice(0,8);
const fpath = `${assetDetails.id}/${fname.slice(0,1)}/${fname.slice(1,8)}`;
writeOps.push(
writeFile(
`${cssDir}/${fpath}.css`,
`${entry.css}\n{display:none!important;}\n`
)
);
entry.css = fname;
cssEntries.push(entry);
}
log(`CSS entries: ${cssEntries.length}`);
if ( cssEntries.length !== 0 ) {
cssDetails.set(assetDetails.id, cssEntries);
}
rulesetDetails.push({
id: assetDetails.id,
name: assetDetails.name,
enabled: assetDetails.enabled,
lang: assetDetails.lang,
homeURL: assetDetails.homeURL,
filters: {
total: details.filterCount,
accepted: details.acceptedFilterCount,
rejected: details.rejectedFilterCount,
total: network.filterCount,
accepted: network.acceptedFilterCount,
rejected: network.rejectedFilterCount,
},
rules: {
total: rules.length,
@ -270,24 +332,11 @@ async function main() {
rejected: bad.length,
regexes: regexes.length,
},
css: {
specific: cssEntries.length,
},
});
writeOps.push(
writeFile(
`${rulesetDir}/${assetDetails.id}.json`,
`${JSON.stringify(good, replacer, 2)}\n`
)
);
regexRulesetDetails.set(assetDetails.id, regexes);
writeOps.push(
writeFile(
`${rulesetDir}/${assetDetails.id}.regexes.json`,
`${JSON.stringify(regexes, replacer, 2)}\n`
)
);
ruleResources.push({
id: assetDetails.id,
enabled: assetDetails.enabled,
@ -321,6 +370,7 @@ async function main() {
name: 'Ads, trackers, miners, and more' ,
enabled: true,
urls: contentURLs,
homeURL: 'https://github.com/uBlockOrigin/uAssets',
});
// Regional rulesets
@ -338,10 +388,11 @@ async function main() {
name: asset.title,
enabled: false,
urls: [ contentURL ],
homeURL: asset.supportURL,
});
}
// Handpicked rulesets
// Handpicked rulesets from assets.json
const handpicked = [ 'block-lan', 'dpollock-0' ];
for ( const id of handpicked ) {
const asset = assets[id];
@ -355,13 +406,30 @@ async function main() {
name: asset.title,
enabled: false,
urls: [ contentURL ],
homeURL: asset.supportURL,
});
}
// Handpicked rulesets from abroad
await rulesetFromURLS({
id: 'stevenblack-hosts',
name: 'Steven Black\'s hosts file',
enabled: false,
urls: [ 'https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts' ],
homeURL: 'https://github.com/StevenBlack/hosts#readme',
});
writeOps.push(
writeFile(
`${rulesetDir}/ruleset-details.json`,
`${JSON.stringify(rulesetDetails, replacer, 2)}\n`
`${JSON.stringify(rulesetDetails, null, 2)}\n`
)
);
writeOps.push(
writeFile(
`${cssDir}/css-specific.json`,
`${JSON.stringify(Array.from(cssDetails), null, 2)}\n`
)
);

View File

@ -75,6 +75,10 @@
.fa-icon > .fa-icon_list-alt {
width: calc(1em * 1792 / 1792);
}
.fa-icon > .fa-icon_sun,
.fa-icon > .fa-icon_sun-o {
width: calc(1em * 1708 / 1792);
}
.fa-icon > .fa-icon_download-alt,
.fa-icon > .fa-icon_font,
.fa-icon > .fa-icon_search,
@ -85,6 +89,9 @@
.fa-icon > .fa-icon_zoom-out {
width: calc(1em * 1664 / 1792);
}
.fa-icon > .fa-icon_magic {
width: calc(1em * 1637 / 1792);
}
.fa-icon > .fa-icon_home {
width: calc(1em * 1612 / 1792);
}

View File

@ -59,6 +59,7 @@ const faIconsInit = (( ) => {
[ 'info-circle', { viewBox: '0 0 1536 1536', path: 'm 1024,1248 0,-160 q 0,-14 -9,-23 -9,-9 -23,-9 l -96,0 0,-512 q 0,-14 -9,-23 -9,-9 -23,-9 l -320,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 96,0 0,320 -96,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 448,0 q 14,0 23,-9 9,-9 9,-23 z M 896,352 896,192 q 0,-14 -9,-23 -9,-9 -23,-9 l -192,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 192,0 q 14,0 23,-9 9,-9 9,-23 z m 640,416 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z' } ],
[ 'list-alt', { viewBox: '0 0 1792 1408', path: 'm 384,1056 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -64,0 q -13,0 -22.5,-9.5 Q 256,1133 256,1120 l 0,-64 q 0,-13 9.5,-22.5 9.5,-9.5 22.5,-9.5 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 Q 365,896 352,896 l -64,0 q -13,0 -22.5,-9.5 Q 256,877 256,864 l 0,-64 q 0,-13 9.5,-22.5 Q 275,768 288,768 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 Q 365,640 352,640 l -64,0 q -13,0 -22.5,-9.5 Q 256,621 256,608 l 0,-64 q 0,-13 9.5,-22.5 Q 275,512 288,512 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 1152,512 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,1133 512,1120 l 0,-64 q 0,-13 9.5,-22.5 9.5,-9.5 22.5,-9.5 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,877 512,864 l 0,-64 q 0,-13 9.5,-22.5 Q 531,768 544,768 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,621 512,608 l 0,-64 q 0,-13 9.5,-22.5 Q 531,512 544,512 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,704 0,-832 q 0,-13 -9.5,-22.5 Q 1645,384 1632,384 l -1472,0 q -13,0 -22.5,9.5 Q 128,403 128,416 l 0,832 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 1472,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 128,-1088 0,1088 q 0,66 -47,113 -47,47 -113,47 l -1472,0 Q 94,1408 47,1361 0,1314 0,1248 L 0,160 Q 0,94 47,47 94,0 160,0 l 1472,0 q 66,0 113,47 47,47 47,113 z' } ],
[ 'lock', { viewBox: '0 0 1152 1408', path: 'm 320,640 512,0 0,-192 q 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 l 0,192 z m 832,96 0,576 q 0,40 -28,68 -28,28 -68,28 l -960,0 Q 56,1408 28,1380 0,1352 0,1312 L 0,736 q 0,-40 28,-68 28,-28 68,-28 l 32,0 0,-192 Q 128,264 260,132 392,0 576,0 q 184,0 316,132 132,132 132,316 l 0,192 32,0 q 40,0 68,28 28,28 28,68 z' } ],
[ 'magic', { viewBox: '0 0 1637 1637', path: 'M 1163,581 1456,288 1349,181 1056,474 Z m 447,-293 q 0,27 -18,45 L 306,1619 q -18,18 -45,18 -27,0 -45,-18 L 18,1421 Q 0,1403 0,1376 0,1349 18,1331 L 1304,45 q 18,-18 45,-18 27,0 45,18 l 198,198 q 18,18 18,45 z M 259,98 l 98,30 -98,30 -30,98 -30,-98 -98,-30 98,-30 30,-98 z M 609,260 805,320 609,380 549,576 489,380 293,320 489,260 549,64 Z m 930,478 98,30 -98,30 -30,98 -30,-98 -98,-30 98,-30 30,-98 z M 899,98 l 98,30 -98,30 -30,98 -30,-98 -98,-30 98,-30 30,-98 z' } ],
[ 'pause-circle-o', { viewBox: '0 0 1536 1536', path: 'M 768,0 Q 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 1536,977 1433,1153.5 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 Z m 0,1312 q 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 z m 96,-224 q -14,0 -23,-9 -9,-9 -9,-23 l 0,-576 q 0,-14 9,-23 9,-9 23,-9 l 192,0 q 14,0 23,9 9,9 9,23 l 0,576 q 0,14 -9,23 -9,9 -23,9 l -192,0 z m -384,0 q -14,0 -23,-9 -9,-9 -9,-23 l 0,-576 q 0,-14 9,-23 9,-9 23,-9 l 192,0 q 14,0 23,9 9,9 9,23 l 0,576 q 0,14 -9,23 -9,9 -23,9 l -192,0 z' } ],
[ 'play-circle-o', { viewBox: '0 0 1536 1536', path: 'm 1184,768 q 0,37 -32,55 l -544,320 q -15,9 -32,9 -16,0 -32,-8 -32,-19 -32,-56 l 0,-640 q 0,-37 32,-56 33,-18 64,1 l 544,320 q 32,18 32,55 z m 128,0 q 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 z m 224,0 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z' } ],
[ 'plus', { viewBox: '0 0 1408 1408', path: 'm 1408,608 0,192 q 0,40 -28,68 -28,28 -68,28 l -416,0 0,416 q 0,40 -28,68 -28,28 -68,28 l -192,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-416 -416,0 Q 56,896 28,868 0,840 0,800 L 0,608 q 0,-40 28,-68 28,-28 68,-28 l 416,0 0,-416 Q 512,56 540,28 568,0 608,0 l 192,0 q 40,0 68,28 28,28 28,68 l 0,416 416,0 q 40,0 68,28 28,28 28,68 z' } ],
@ -69,6 +70,8 @@ const faIconsInit = (( ) => {
[ 'search', { viewBox: '0 0 1664 1664', path: 'M 1152,704 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,52 -38,90 -38,38 -90,38 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z' } ],
[ 'sliders', { viewBox: '0 0 1536 1408', path: 'm 352,1152 0,128 -352,0 0,-128 352,0 z m 352,-128 q 26,0 45,19 19,19 19,45 l 0,256 q 0,26 -19,45 -19,19 -45,19 l -256,0 q -26,0 -45,-19 -19,-19 -19,-45 l 0,-256 q 0,-26 19,-45 19,-19 45,-19 l 256,0 z m 160,-384 0,128 -864,0 0,-128 864,0 z m -640,-512 0,128 -224,0 0,-128 224,0 z m 1312,1024 0,128 -736,0 0,-128 736,0 z M 576,0 q 26,0 45,19 19,19 19,45 l 0,256 q 0,26 -19,45 -19,19 -45,19 l -256,0 q -26,0 -45,-19 -19,-19 -19,-45 L 256,64 Q 256,38 275,19 294,0 320,0 l 256,0 z m 640,512 q 26,0 45,19 19,19 19,45 l 0,256 q 0,26 -19,45 -19,19 -45,19 l -256,0 q -26,0 -45,-19 -19,-19 -19,-45 l 0,-256 q 0,-26 19,-45 19,-19 45,-19 l 256,0 z m 320,128 0,128 -224,0 0,-128 224,0 z m 0,-512 0,128 -864,0 0,-128 864,0 z' } ],
[ 'spinner', { viewBox: '0 0 1664 1728', path: 'm 462,1394 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -52,0 -90,-38 -38,-38 -38,-90 0,-53 37.5,-90.5 37.5,-37.5 90.5,-37.5 53,0 90.5,37.5 37.5,37.5 37.5,90.5 z m 498,206 q 0,53 -37.5,90.5 Q 885,1728 832,1728 779,1728 741.5,1690.5 704,1653 704,1600 q 0,-53 37.5,-90.5 37.5,-37.5 90.5,-37.5 53,0 90.5,37.5 Q 960,1547 960,1600 Z M 256,896 q 0,53 -37.5,90.5 Q 181,1024 128,1024 75,1024 37.5,986.5 0,949 0,896 0,843 37.5,805.5 75,768 128,768 q 53,0 90.5,37.5 Q 256,843 256,896 Z m 1202,498 q 0,52 -38,90 -38,38 -90,38 -53,0 -90.5,-37.5 -37.5,-37.5 -37.5,-90.5 0,-53 37.5,-90.5 37.5,-37.5 90.5,-37.5 53,0 90.5,37.5 37.5,37.5 37.5,90.5 z M 494,398 q 0,66 -47,113 -47,47 -113,47 -66,0 -113,-47 -47,-47 -47,-113 0,-66 47,-113 47,-47 113,-47 66,0 113,47 47,47 47,113 z m 1170,498 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -53,0 -90.5,-37.5 Q 1408,949 1408,896 q 0,-53 37.5,-90.5 37.5,-37.5 90.5,-37.5 53,0 90.5,37.5 Q 1664,843 1664,896 Z M 1024,192 q 0,80 -56,136 -56,56 -136,56 -80,0 -136,-56 -56,-56 -56,-136 0,-80 56,-136 56,-56 136,-56 80,0 136,56 56,56 56,136 z m 530,206 q 0,93 -66,158.5 -66,65.5 -158,65.5 -93,0 -158.5,-65.5 Q 1106,491 1106,398 q 0,-92 65.5,-158 65.5,-66 158.5,-66 92,0 158,66 66,66 66,158 z' } ],
[ 'sun', { viewBox: '0 0 1708 1792', path: 'm 1706,1172.5 c -3,10 -11,17 -20,20 l -292,96 v 306 c 0,10 -5,20 -13,26 -9,6 -19,8 -29,4 l -292,-94 -180,248 c -6,8 -16,13 -26,13 -10,0 -20,-5 -26,-13 l -180,-248 -292,94 c -10,4 -20,2 -29,-4 -8,-6 -13,-16 -13,-26 v -306 l -292,-96 c -9,-3 -17,-10 -20,-20 -3,-10 -2,-21 4,-29 l 180,-248 -180,-248 c -6,-9 -7,-19 -4,-29 3,-10 11,-17 20,-20 l 292,-96 v -306 c 0,-10 5,-20 13,-26 9,-6 19,-8 29,-4 l 292,94 180,-248 c 12,-16 40,-16 52,0 L 1060,260.5 l 292,-94 c 10,-4 20,-2 29,4 8,6 13,16 13,26 v 306 l 292,96 c 9,3 17,10 20,20 3,10 2,20 -4,29 l -180,248 180,248 c 6,8 7,19 4,29 z' } ],
[ 'sun-o', { viewBox: '0 0 1708 1792', path: 'm 1430,895.5 c 0,-318 -258,-576 -576,-576 -318,0 -576,258 -576,576 0,318 258,576 576,576 C 1172,1471.5 1430,1213.5 1430,895.5 Z m 276,277 c -3,10 -11,17 -20,20 l -292,96 v 306 c 0,10 -5,20 -13,26 -9,6 -19,8 -29,4 l -292,-94 -180,248 c -6,8 -16,13 -26,13 -10,0 -20,-5 -26,-13 l -180,-248 -292,94 c -10,4 -20,2 -29,-4 -8,-6 -13,-16 -13,-26 v -306 l -292,-96 c -9,-3 -17,-10 -20,-20 -3,-10 -2,-21 4,-29 l 180,-248 -180,-248 c -6,-9 -7,-19 -4,-29 3,-10 11,-17 20,-20 l 292,-96 v -306 c 0,-10 5,-20 13,-26 9,-6 19,-8 29,-4 l 292,94 180,-248 c 12,-16 40,-16 52,0 L 1060,260.5 l 292,-94 c 10,-4 20,-2 29,4 8,6 13,16 13,26 v 306 l 292,96 c 9,3 17,10 20,20 3,10 2,20 -4,29 l -180,248 180,248 c 6,8 7,19 4,29 z' } ],
[ 'times', { viewBox: '0 0 1188 1188', path: 'm 1188,956 q 0,40 -28,68 l -136,136 q -28,28 -68,28 -40,0 -68,-28 L 594,866 300,1160 q -28,28 -68,28 -40,0 -68,-28 L 28,1024 Q 0,996 0,956 0,916 28,888 L 322,594 28,300 Q 0,272 0,232 0,192 28,164 L 164,28 Q 192,0 232,0 272,0 300,28 L 594,322 888,28 q 28,-28 68,-28 40,0 68,28 l 136,136 q 28,28 28,68 0,40 -28,68 l -294,294 294,294 q 28,28 28,68 z' } ],
[ 'trash-o', { viewBox: '0 0 1408 1536', path: 'm 512,608 v 576 q 0,14 -9,23 -9,9 -23,9 h -64 q -14,0 -23,-9 -9,-9 -9,-23 V 608 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 256,0 v 576 q 0,14 -9,23 -9,9 -23,9 h -64 q -14,0 -23,-9 -9,-9 -9,-23 V 608 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 256,0 v 576 q 0,14 -9,23 -9,9 -23,9 h -64 q -14,0 -23,-9 -9,-9 -9,-23 V 608 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 128,724 V 384 H 256 v 948 q 0,22 7,40.5 7,18.5 14.5,27 7.5,8.5 10.5,8.5 h 832 q 3,0 10.5,-8.5 7.5,-8.5 14.5,-27 7,-18.5 7,-40.5 z M 480,256 H 928 L 880,139 q -7,-9 -17,-11 H 546 q -10,2 -17,11 z m 928,32 v 64 q 0,14 -9,23 -9,9 -23,9 h -96 v 948 q 0,83 -47,143.5 -47,60.5 -113,60.5 H 288 q -66,0 -113,-58.5 Q 128,1419 128,1336 V 384 H 32 Q 18,384 9,375 0,366 0,352 v -64 q 0,-14 9,-23 9,-9 23,-9 H 341 L 411,89 Q 426,52 465,26 504,0 544,0 h 320 q 40,0 79,26 39,26 54,63 l 70,167 h 309 q 14,0 23,9 9,9 9,23 z' } ],
[ 'undo', { viewBox: '0 0 1536 1536', path: 'm 1536,768 q 0,156 -61,298 -61,142 -164,245 -103,103 -245,164 -142,61 -298,61 -172,0 -327,-72.5 Q 286,1391 177,1259 q -7,-10 -6.5,-22.5 0.5,-12.5 8.5,-20.5 l 137,-138 q 10,-9 25,-9 16,2 23,12 73,95 179,147 106,52 225,52 104,0 198.5,-40.5 Q 1061,1199 1130,1130 1199,1061 1239.5,966.5 1280,872 1280,768 1280,664 1239.5,569.5 1199,475 1130,406 1061,337 966.5,296.5 872,256 768,256 670,256 580,291.5 490,327 420,393 l 137,138 q 31,30 14,69 -17,40 -59,40 H 64 Q 38,640 19,621 0,602 0,576 V 128 Q 0,86 40,69 79,52 109,83 L 239,212 Q 346,111 483.5,55.5 621,0 768,0 q 156,0 298,61 142,61 245,164 103,103 164,245 61,142 61,298 z' } ],

View File

@ -19,6 +19,8 @@
Home: https://github.com/gorhill/uBlock
*/
/* globals browser */
'use strict';
/******************************************************************************/
@ -161,7 +163,8 @@ const onMessage = function(request, sender, callback) {
env: vAPI.webextFlavor.env,
};
const t0 = Date.now();
dnrRulesetFromRawLists(listPromises, options).then(details => {
dnrRulesetFromRawLists(listPromises, options).then(result => {
const { network } = result;
const replacer = (k, v) => {
if ( k.startsWith('__') ) { return; }
if ( Array.isArray(v) ) {
@ -193,13 +196,13 @@ const onMessage = function(request, sender, callback) {
rule.action.type === 'redirect' &&
rule.action.redirect.transform !== undefined;
const runtime = Date.now() - t0;
const { ruleset } = details;
const { ruleset } = network;
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}`,
`Filters count: ${network.filterCount}`,
`Accepted filter count: ${network.acceptedFilterCount}`,
`Rejected filter count: ${network.rejectedFilterCount}`,
`Resulting DNR rule count: ${ruleset.length}`,
];
const good = ruleset.filter(rule =>
@ -237,6 +240,12 @@ const onMessage = function(request, sender, callback) {
isUnsupported(rule)
);
out.push(`+ Unsupported filters (${bad.length}): ${JSON.stringify(bad, replacer, 2)}`);
out.push(`\n+ Cosmetic filters: ${result.cosmetic.length}`);
for ( const details of result.cosmetic ) {
out.push(` ${JSON.stringify(details)}`);
}
callback(out.join('\n'));
});
return;

View File

@ -34,6 +34,92 @@ import {
/******************************************************************************/
function addExtendedToDNR(context, parser) {
if ( parser.category !== parser.CATStaticExtFilter ) { return false; }
if ( (parser.flavorBits & parser.BITFlavorUnsupported) !== 0 ) {
return true;
}
// Scriptlet injection
if ( (parser.flavorBits & parser.BITFlavorExtScriptlet) !== 0 ) {
return true;
}
// Response header filtering
if ( (parser.flavorBits & parser.BITFlavorExtResponseHeader) !== 0 ) {
return true;
}
// HTML filtering
if ( (parser.flavorBits & parser.BITFlavorExtHTML) !== 0 ) {
return true;
}
// Cosmetic filtering
if ( context.cosmeticFilters === undefined ) {
context.cosmeticFilters = new Map();
}
// https://github.com/chrisaljoudi/uBlock/issues/151
// Negated hostname means the filter applies to all non-negated hostnames
// of same filter OR globally if there is no non-negated hostnames.
for ( const { hn, not, bad } of parser.extOptions() ) {
if ( bad ) { continue; }
const { compiled, exception } = parser.result;
if ( compiled.startsWith('{') ) { continue; }
if ( exception ) { continue; }
if ( hn.endsWith('.*') ) { continue; }
let cssdetails = context.cosmeticFilters.get(compiled);
if ( cssdetails === undefined ) {
cssdetails = {
};
context.cosmeticFilters.set(compiled, cssdetails);
}
if ( not ) {
if ( cssdetails.excludeMatches === undefined ) {
cssdetails.excludeMatches = [];
}
cssdetails.excludeMatches.push(hn);
continue;
}
if ( cssdetails.matches === undefined ) {
cssdetails.matches = [];
}
if ( cssdetails.matches.includes('*') ) { continue; }
if ( hn === '*' ) {
cssdetails.matches = [ '*' ];
continue;
}
cssdetails.matches.push(hn);
}
}
/******************************************************************************/
function optimizeCosmeticFilters(filters) {
if ( filters === undefined ) { return []; }
const merge = new Map();
for ( const [ selector, details ] of filters ) {
const json = JSON.stringify(details);
let entries = merge.get(json);
if ( entries === undefined ) {
entries = new Set();
merge.set(json, entries);
}
entries.add(selector);
}
const out = [];
for ( const [ json, selectors ] of merge ) {
const details = JSON.parse(json);
details.css = Array.from(selectors).join(',\n');
out.push(details);
}
return out;
}
/******************************************************************************/
function addToDNR(context, list) {
const writer = new CompiledListWriter();
const lineIter = new LineIterator(
@ -58,7 +144,11 @@ function addToDNR(context, list) {
parser.analyze(line);
if ( parser.shouldIgnore() ) { continue; }
if ( parser.category !== parser.CATStaticNetFilter ) { continue; }
if ( parser.category !== parser.CATStaticNetFilter ) {
addExtendedToDNR(context, parser);
continue;
}
// https://github.com/gorhill/uBlock/issues/2599
// convert hostname to punycode if needed
@ -98,7 +188,11 @@ async function dnrRulesetFromRawLists(lists, options = {}) {
}
}
await Promise.all(toLoad);
return staticNetFilteringEngine.dnrFromCompiled('end', context);
return {
network: staticNetFilteringEngine.dnrFromCompiled('end', context),
cosmetic: optimizeCosmeticFilters(context.cosmeticFilters),
};
}
/******************************************************************************/

View File

@ -51,7 +51,7 @@ if [ "$1" != "quick" ]; then
cp platform/mv3/*.js $TMPDIR/
cp assets/assets.json $TMPDIR/
cd $TMPDIR
node --no-warnings make-rulesets.js output=$DES quick=$QUICK
node --no-warnings make-rulesets.js output=$DES
cd - > /dev/null
rm -rf $TMPDIR
fi