1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-07-08 12:57:57 +02:00

[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>
<div id="templates" style="display: none;"> <div id="templates">
<div class="groupEntry"> <div class="groupEntry">
<div class="geDetails"><span class="geName"></span> <span class="geCount"></span></div> <div class="geDetails"><span class="geName"></span> <span class="geCount"></span></div>
<div class="listEntries"></div> <div class="listEntries"></div>

View File

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

View File

@ -120,13 +120,13 @@ body.needSave #revertRules {
#rulesetStats { #rulesetStats {
padding: 0 var(--popup-gap-thin); padding: 0 var(--popup-gap-thin);
} }
#rulesetStats > h1 { #rulesetStats .rulesetDetails h1 {
font-size: 1em; font-size: 1em;
margin-bottom: var(--popup-gap-thin); margin: 0.5em 0;
} }
#rulesetStats > p { #rulesetStats .rulesetDetails p {
font-size: var(--font-size-smaller); 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 { .itemRibbon {
@ -147,9 +147,8 @@ body.needSave #revertRules {
display: grid; display: grid;
grid-auto-columns: 1fr; grid-auto-columns: 1fr;
grid-auto-flow: column; grid-auto-flow: column;
grid-template: auto / repeat(4, 1fr); grid-template: auto / repeat(5, 1fr);
justify-items: center; justify-items: center;
margin: 0;
white-space: normal; white-space: normal;
} }
.toolRibbon .tool { .toolRibbon .tool {
@ -178,9 +177,20 @@ body.needSave #revertRules {
body.mobile.no-tooltips .toolRibbon .tool { body.mobile.no-tooltips .toolRibbon .tool {
font-size: 1.6em; font-size: 1.6em;
} }
.toolRibbon.genericTools {
margin-bottom: 0;
}
#basicTools { body:not(.hasGreatPowers) [data-i18n-title="popupGrantGreatPowers"],
margin-top: var(--default-gap); 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 { #moreOrLess {
@ -204,7 +214,7 @@ body.mobile.no-tooltips .toolRibbon .tool {
border-inline-start: 1px solid var(--surface-1); border-inline-start: 1px solid var(--surface-1);
text-align: end; text-align: end;
} }
body[data-section="a"] #moreButton { body[data-section="a b"] #moreButton {
pointer-events: none; pointer-events: none;
visibility: hidden; visibility: hidden;
} }
@ -215,6 +225,9 @@ body[data-section=""] #lessButton {
body:not([data-section~="a"]) [data-section="a"] { body:not([data-section~="a"]) [data-section="a"] {
display: none; display: none;
} }
body:not([data-section~="b"]) [data-section="b"] {
display: none;
}
/* configurable UI elements */ /* configurable UI elements */
:root:not(.mobile) .toolRibbon .caption, :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(--surface-2);
/* background-color: var(--popup-toolbar-surface-hover); */ /* 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); const on = enabledRulesets.includes(ruleset.id);
li.classList.toggle('checked', on); li.classList.toggle('checked', on);
let elem;
if ( dom.attr(li, 'data-listkey') !== ruleset.id ) { if ( dom.attr(li, 'data-listkey') !== ruleset.id ) {
dom.attr(li, 'data-listkey', ruleset.id); dom.attr(li, 'data-listkey', ruleset.id);
qs$('input[type="checkbox"]', li).checked = on; qs$('input[type="checkbox"]', li).checked = on;
qs$('.listname', li).textContent = ruleset.name || ruleset.id; qs$('.listname', li).textContent = ruleset.name || ruleset.id;
dom.removeClass(li, 'toRemove'); dom.cl.remove(li, 'toRemove');
if ( ruleset.supportName ) { if ( ruleset.homeURL ) {
dom.addClass(li, 'support'); dom.cl.add(li, 'support');
elem = qs$('a.support', li); const elem = qs$('a.support', li);
dom.attr(elem, 'href', ruleset.supportURL); dom.attr(elem, 'href', ruleset.homeURL);
dom.attr(elem, 'title', ruleset.supportName);
} else { } else {
dom.removeClass(li, 'support'); dom.cl.remove(li, 'support');
} }
if ( ruleset.instructionURL ) { if ( ruleset.instructionURL ) {
dom.addClass(li, 'mustread'); dom.cl.add(li, 'mustread');
dom.attr(qs$('a.mustread', li), 'href', ruleset.instructionURL); dom.attr(qs$('a.mustread', li), 'href', ruleset.instructionURL);
} else { } else {
dom.removeClass(li, 'mustread'); dom.cl.remove(li, 'mustread');
} }
dom.toggleClass(li, 'isDefault', ruleset.isDefault === true); dom.cl.toggle(li, 'isDefault', ruleset.isDefault === true);
dom.toggleClass(li, 'unused', hideUnused && !on); dom.cl.toggle(li, 'unused', hideUnused && !on);
} }
// https://github.com/gorhill/uBlock/issues/1429 // https://github.com/gorhill/uBlock/issues/1429
if ( !soft ) { if ( !soft ) {
@ -141,7 +139,7 @@ const renderFilterLists = function(soft) {
// Incremental rendering: this will allow us to easily discard unused // Incremental rendering: this will allow us to easily discard unused
// DOM list entries. // DOM list entries.
dom.addClass( dom.cl.add(
qsa$('#lists .listEntries .listEntry[data-listkey]'), qsa$('#lists .listEntries .listEntry[data-listkey]'),
'discard' '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 ) { for ( const [ groupKey, groupRulesets ] of groups ) {
let liGroup = liFromListGroup(groupKey, groupRulesets); let liGroup = liFromListGroup(groupKey, groupRulesets);
@ -193,7 +191,7 @@ const renderFilterLists = function(soft) {
/******************************************************************************/ /******************************************************************************/
const renderWidgets = function() { const renderWidgets = function() {
dom.toggleClass( dom.cl.toggle(
qs$('#buttonApply'), qs$('#buttonApply'),
'disabled', 'disabled',
filteringSettingsHash === hashFromCurrentFromSettings() filteringSettingsHash === hashFromCurrentFromSettings()
@ -231,12 +229,16 @@ const hashFromCurrentFromSettings = function() {
return hash.join(); return hash.join();
}; };
self.hasUnsavedData = function() {
return hashFromCurrentFromSettings() !== filteringSettingsHash;
};
/******************************************************************************/ /******************************************************************************/
function onListsetChanged(ev) { function onListsetChanged(ev) {
const input = ev.target; const input = ev.target;
const li = input.closest('.listEntry'); const li = input.closest('.listEntry');
dom.toggleClass(li, 'checked', input.checked); dom.cl.toggle(li, 'checked', input.checked);
renderWidgets(); renderWidgets();
} }
@ -265,7 +267,7 @@ const applyEnabledRulesets = async function() {
}; };
const buttonApplyHandler = async function() { const buttonApplyHandler = async function() {
dom.removeClass(qs$('#buttonApply'), 'enabled'); dom.cl.remove(qs$('#buttonApply'), 'enabled');
await applyEnabledRulesets(); await applyEnabledRulesets();
renderWidgets(); renderWidgets();
}; };
@ -298,7 +300,7 @@ const toggleHideUnusedLists = function(which) {
hideUnusedSet.add(which); hideUnusedSet.add(which);
} }
document.body.classList.toggle('hideUnused', mustHide); document.body.classList.toggle('hideUnused', mustHide);
dom.toggleClass(qsa$('.groupEntry[data-groupkey]'), 'hideUnused', mustHide); dom.cl.toggle(qsa$('.groupEntry[data-groupkey]'), 'hideUnused', mustHide);
} else { } else {
const doesHide = hideUnusedSet.has(which); const doesHide = hideUnusedSet.has(which);
if ( doesHide ) { if ( doesHide ) {
@ -308,11 +310,11 @@ const toggleHideUnusedLists = function(which) {
} }
mustHide = doesHide === doesHideAll; mustHide = doesHide === doesHideAll;
groupSelector = `.groupEntry[data-groupkey="${which}"]`; 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)`) ) { 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]'), elem.closest('.listEntry[data-listkey]'),
'unused', 'unused',
mustHide 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({ sendMessage({
what: 'getRulesetData', what: 'getRulesetData',
}).then(data => { }).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) { async function updateRegexRules(dynamicRules) {
// Avoid testing already tested regexes // Avoid testing already tested regexes
const validRegexSet = new Set( const validRegexSet = new Set(
@ -122,7 +114,8 @@ async function updateRegexRules(dynamicRules) {
const toFetch = []; const toFetch = [];
for ( const details of rulesetDetails.values() ) { for ( const details of rulesetDetails.values() ) {
if ( details.enabled !== true ) { continue; } 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); const regexRulesets = await Promise.all(toFetch);
@ -303,11 +296,7 @@ async function getEnabledRulesetsStats() {
for ( const id of ids ) { for ( const id of ids ) {
const ruleset = rulesetDetails.get(id); const ruleset = rulesetDetails.get(id);
if ( ruleset === undefined ) { continue; } if ( ruleset === undefined ) { continue; }
out.push({ out.push(ruleset);
name: ruleset.name,
filterCount: ruleset.filters.accepted,
ruleCount: ruleset.rules.accepted,
});
} }
return out; 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() { async function start() {
// Fetch enabled rulesets and dynamic rules // Fetch enabled rulesets and dynamic rules
const dynamicRules = await dnr.getDynamicRules(); const dynamicRules = await dnr.getDynamicRules();
@ -352,7 +434,7 @@ async function start() {
} }
// Fetch ruleset details // Fetch ruleset details
await fetchJSON('ruleset-details').then(entries => { await fetchJSON('/rulesets/ruleset-details').then(entries => {
if ( entries === undefined ) { return; } if ( entries === undefined ) { return; }
for ( const entry of entries ) { for ( const entry of entries ) {
rulesetDetails.set(entry.id, entry); rulesetDetails.set(entry.id, entry);
@ -386,61 +468,11 @@ async function start() {
dnr.setExtensionActionOptions({ displayActionCountAsBadgeText: true }); 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 ( ) => { (async ( ) => {
await start(); 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 ( target === null ) { return []; }
if ( Array.isArray(target) ) { return target; } if ( Array.isArray(target) ) { return target; }
return target instanceof Element return target instanceof Element
? [ target ] ? [ target ]
: Array.from(target); : Array.from(target);
} };
function makeEventHandler(selector, callback) { const makeEventHandler = (selector, callback) => {
return function(event) { return function(event) {
const dispatcher = event.currentTarget; const dispatcher = event.currentTarget;
if ( if (
@ -52,30 +52,11 @@ function makeEventHandler(selector, callback) {
callback.call(receiver, event); callback.call(receiver, event);
} }
}; };
} };
/******************************************************************************/ /******************************************************************************/
class dom { 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) { static attr(target, attr, value = undefined) {
for ( const elem of normalizeTarget(target) ) { for ( const elem of normalizeTarget(target) ) {
if ( value === undefined ) { 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) { static remove(target) {
for ( const elem of normalizeTarget(target) ) { for ( const elem of normalizeTarget(target) ) {
elem.remove(); 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; 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$ }; 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 { browser, sendMessage } from './ext.js';
import { dom, qs$ } from './dom.js';
import { i18n$ } from './i18n.js'; import { i18n$ } from './i18n.js';
import { simpleStorage } from './storage.js'; import { simpleStorage } from './storage.js';
/******************************************************************************/ /******************************************************************************/
let currentTab = {}; 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; return;
} }
if ( url instanceof URL === false ) { 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({ const newTrustedState = await sendMessage({
what: 'toggleTrustedSiteDirective', what: 'toggleTrustedSiteDirective',
origin: url.origin, origin: url.origin,
@ -53,101 +77,37 @@ async function toggleTrustedSiteDirective() {
}).catch(( ) => }).catch(( ) =>
targetTrustedState === false targetTrustedState === false
); );
document.body.classList.toggle('off', newTrustedState === true);
document.body.classList.toggle( dom.cl.toggle(dom.body, 'off', newTrustedState === true);
'needReload', onStateHashChanged();
newTrustedState !== originalTrustedState
);
} }
dom.on(qs$('#switch'), 'click', toggleTrustedSiteDirective);
/******************************************************************************/ /******************************************************************************/
function reloadTab(ev) { function reloadTab(ev) {
browser.tabs.reload(currentTab.id, { browser.tabs.reload(currentTab.id, {
bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey, bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey,
}); });
document.body.classList.remove('needReload'); dom.cl.remove(dom.body, 'needReload');
originalTrustedState = document.body.classList.contains('off'); originalStateHash = getCurrentStateHash();
} }
/******************************************************************************/ dom.on(qs$('#refresh'), 'click', reloadTab);
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();
/******************************************************************************/ /******************************************************************************/
// The popup panel is made of sections. Visibility of sections can be // The popup panel is made of sections. Visibility of sections can be
// toggled on/off. // toggled on/off.
const maxNumberOfSections = 1; const maxNumberOfSections = 2;
const sectionBitsFromAttribute = function() { const sectionBitsFromAttribute = function() {
const attr = document.body.dataset.section; const value = dom.body.dataset.section;
if ( attr === '' ) { return 0; } if ( value === '' ) { return 0; }
let bits = 0; let bits = 0;
for ( const c of attr.split(' ') ) { for ( const c of value.split(' ') ) {
bits |= 1 << (c.charCodeAt(0) - 97); bits |= 1 << (c.charCodeAt(0) - 97);
} }
return bits; return bits;
@ -156,13 +116,13 @@ const sectionBitsFromAttribute = function() {
const sectionBitsToAttribute = function(bits) { const sectionBitsToAttribute = function(bits) {
if ( typeof bits !== 'number' ) { return; } if ( typeof bits !== 'number' ) { return; }
if ( isNaN(bits) ) { return; } if ( isNaN(bits) ) { return; }
const attr = []; const value = [];
for ( let i = 0; i < maxNumberOfSections; i++ ) { for ( let i = 0; i < maxNumberOfSections; i++ ) {
const bit = 1 << i; const bit = 1 << i;
if ( (bits & bit) === 0 ) { continue; } 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) { async function toggleSections(more) {
@ -186,13 +146,111 @@ simpleStorage.getItem('popupPanelSections').then(s => {
sectionBitsToAttribute(parseInt(s, 10) || 0); sectionBitsToAttribute(parseInt(s, 10) || 0);
}); });
document.querySelector('#moreButton').addEventListener('click', ( ) => { dom.on(qs$('#moreButton'), 'click', ( ) => {
toggleSections(true); toggleSections(true);
}); });
document.querySelector('#lessButton').addEventListener('click', ( ) => { dom.on(qs$('#lessButton'), 'click', ( ) => {
toggleSections(false); 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", "minimum_chrome_version": "101.0",
"name": "__MSG_extName__", "name": "__MSG_extName__",
"options_page": "dashboard.html", "options_page": "dashboard.html",
"optional_host_permissions": [
"<all_urls>"
],
"permissions": [ "permissions": [
"activeTab", "activeTab",
"declarativeNetRequest" "declarativeNetRequest",
"scripting"
], ],
"short_name": "uBO Lite", "short_name": "uBO Lite",
"version": "0.1" "version": "0.1"

View File

@ -40,18 +40,20 @@
</div> </div>
<div id="hostname"><span></span>&shy;<span></span></div> <div id="hostname"><span></span>&shy;<span></span></div>
</div> </div>
<div id="basicTools" class="toolRibbon" data-more="c"> <div class="toolRibbon pageTools">
<span class="fa-icon tool needPick" data-i18n-title="popupTipZapper">bolt<span class="caption"></span></span> <span id="toggleGreatPowers">
<span class="fa-icon tool needPick" data-i18n-title="popupTipPicker">eye-dropper<span class="caption"></span></span> <span class="fa-icon tool enabled" data-i18n-title="popupGrantGreatPowers">sun-o<span class="caption"></span></span>
<span class="fa-icon tool needPick" data-i18n-title="popupTipReport">comment-alt<span class="caption"></span></span> <span class="fa-icon tool enabled" data-i18n-title="popupRevokeGreatPowers">sun<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> </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> <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> </div>
<hr data-section="a">
<div id="rulesetStats" data-section="a"> <div id="rulesetStats" data-section="a">
</div> </div>
<hr> <hr data-section="a">
<div id="moreOrLess" class=""> <div id="moreOrLess">
<span id="moreButton"> <span id="moreButton">
<span data-i18n="popupMoreButton">_</span>&emsp;<span class="fa-icon">angle-up</span> <span data-i18n="popupMoreButton">_</span>&emsp;<span class="fa-icon">angle-up</span>
</span> </span>
@ -61,6 +63,10 @@
</div> </div>
</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/fa-icons.js"></script>
<script src="js/i18n.js" type="module"></script> <script src="js/i18n.js" type="module"></script>
<script src="js/popup.js" type="module"></script> <script src="js/popup.js" type="module"></script>

View File

@ -25,7 +25,9 @@
import fs from 'fs/promises'; import fs from 'fs/promises';
import https from 'https'; import https from 'https';
import path from 'path';
import process from 'process'; import process from 'process';
import { createHash } from 'crypto';
import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js'; import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js';
import { StaticFilteringParser } from './js/static-filtering-parser.js'; import { StaticFilteringParser } from './js/static-filtering-parser.js';
@ -91,25 +93,51 @@ 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) => { return new Promise((resolve, reject) => {
log(`\tFetching ${url}`); const fname = urlToFileName(url);
https.get(url, response => { fs.readFile(`${cacheDir}/${fname}`, { encoding: 'utf8' }).then(content => {
const data = []; log(`\tFetched local ${url}`);
response.on('data', chunk => { resolve({ url, content });
data.push(chunk.toString()); }).catch(( ) => {
log(`\tFetching remote ${url}`);
https.get(url, response => {
const data = [];
response.on('data', chunk => {
data.push(chunk.toString());
});
response.on('end', ( ) => {
const content = data.join('');
try {
writeFile(`${cacheDir}/${fname}`, content);
} catch (ex) {
}
resolve({ url, content });
});
}).on('error', error => {
reject(error);
}); });
response.on('end', ( ) => {
resolve({ url, content: data.join('') });
});
}).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);
};
/******************************************************************************/
async function main() { async function main() {
const env = [ 'chromium' ]; const env = [ 'chromium' ];
@ -117,7 +145,7 @@ async function main() {
const writeOps = []; const writeOps = [];
const ruleResources = []; const ruleResources = [];
const rulesetDetails = []; const rulesetDetails = [];
const regexRulesetDetails = new Map(); const cssDetails = new Map();
const outputDir = commandLineArgs.get('output') || '.'; const outputDir = commandLineArgs.get('output') || '.';
// Get manifest content // Get manifest content
@ -159,11 +187,8 @@ async function main() {
}; };
const rulesetDir = `${outputDir}/rulesets`; const rulesetDir = `${outputDir}/rulesets`;
const rulesetDirPromise = fs.mkdir(`${rulesetDir}`, { recursive: true }); const cacheDir = `${outputDir}/../mv3-data`;
const cssDir = `${outputDir}/content-css`;
const writeFile = (path, data) =>
rulesetDirPromise.then(( ) =>
fs.writeFile(path, data));
const rulesetFromURLS = async function(assetDetails) { const rulesetFromURLS = async function(assetDetails) {
log('============================'); log('============================');
@ -187,7 +212,7 @@ async function main() {
} }
fetchedURLs.add(part.url); fetchedURLs.add(part.url);
newParts.push( newParts.push(
fetchList(part.url).then(details => { fetchList(part.url, cacheDir).then(details => {
const { url } = details; const { url } = details;
const content = details.content.trim(); const content = details.content.trim();
if ( typeof content === 'string' && content !== '' ) { if ( typeof content === 'string' && content !== '' ) {
@ -213,11 +238,12 @@ async function main() {
return; return;
} }
const details = await dnrRulesetFromRawLists([ { name: assetDetails.id, text } ], { env }); const results = await dnrRulesetFromRawLists([ { name: assetDetails.id, text } ], { env });
const { ruleset: rules } = details; const { network } = results;
log(`Input filter count: ${details.filterCount}`); const { ruleset: rules } = network;
log(`\tAccepted filter count: ${details.acceptedFilterCount}`); log(`Input filter count: ${network.filterCount}`);
log(`\tRejected filter count: ${details.rejectedFilterCount}`); log(`\tAccepted filter count: ${network.acceptedFilterCount}`);
log(`\tRejected filter count: ${network.rejectedFilterCount}`);
log(`Output rule count: ${rules.length}`); log(`Output rule count: ${rules.length}`);
const good = rules.filter(rule => isGood(rule) && isRegex(rule) === false); const good = rules.filter(rule => isGood(rule) && isRegex(rule) === false);
@ -253,15 +279,51 @@ async function main() {
true 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({ rulesetDetails.push({
id: assetDetails.id, id: assetDetails.id,
name: assetDetails.name, name: assetDetails.name,
enabled: assetDetails.enabled, enabled: assetDetails.enabled,
lang: assetDetails.lang, lang: assetDetails.lang,
homeURL: assetDetails.homeURL,
filters: { filters: {
total: details.filterCount, total: network.filterCount,
accepted: details.acceptedFilterCount, accepted: network.acceptedFilterCount,
rejected: details.rejectedFilterCount, rejected: network.rejectedFilterCount,
}, },
rules: { rules: {
total: rules.length, total: rules.length,
@ -270,24 +332,11 @@ async function main() {
rejected: bad.length, rejected: bad.length,
regexes: regexes.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({ ruleResources.push({
id: assetDetails.id, id: assetDetails.id,
enabled: assetDetails.enabled, enabled: assetDetails.enabled,
@ -321,6 +370,7 @@ async function main() {
name: 'Ads, trackers, miners, and more' , name: 'Ads, trackers, miners, and more' ,
enabled: true, enabled: true,
urls: contentURLs, urls: contentURLs,
homeURL: 'https://github.com/uBlockOrigin/uAssets',
}); });
// Regional rulesets // Regional rulesets
@ -338,10 +388,11 @@ async function main() {
name: asset.title, name: asset.title,
enabled: false, enabled: false,
urls: [ contentURL ], urls: [ contentURL ],
homeURL: asset.supportURL,
}); });
} }
// Handpicked rulesets // Handpicked rulesets from assets.json
const handpicked = [ 'block-lan', 'dpollock-0' ]; const handpicked = [ 'block-lan', 'dpollock-0' ];
for ( const id of handpicked ) { for ( const id of handpicked ) {
const asset = assets[id]; const asset = assets[id];
@ -355,13 +406,30 @@ async function main() {
name: asset.title, name: asset.title,
enabled: false, enabled: false,
urls: [ contentURL ], 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( writeOps.push(
writeFile( writeFile(
`${rulesetDir}/ruleset-details.json`, `${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 { .fa-icon > .fa-icon_list-alt {
width: calc(1em * 1792 / 1792); 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_download-alt,
.fa-icon > .fa-icon_font, .fa-icon > .fa-icon_font,
.fa-icon > .fa-icon_search, .fa-icon > .fa-icon_search,
@ -85,6 +89,9 @@
.fa-icon > .fa-icon_zoom-out { .fa-icon > .fa-icon_zoom-out {
width: calc(1em * 1664 / 1792); width: calc(1em * 1664 / 1792);
} }
.fa-icon > .fa-icon_magic {
width: calc(1em * 1637 / 1792);
}
.fa-icon > .fa-icon_home { .fa-icon > .fa-icon_home {
width: calc(1em * 1612 / 1792); 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' } ], [ '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' } ], [ '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' } ], [ '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' } ], [ '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' } ], [ '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' } ], [ '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' } ], [ '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' } ], [ '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' } ], [ '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' } ], [ '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' } ], [ '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' } ], [ '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 Home: https://github.com/gorhill/uBlock
*/ */
/* globals browser */
'use strict'; 'use strict';
/******************************************************************************/ /******************************************************************************/
@ -161,7 +163,8 @@ const onMessage = function(request, sender, callback) {
env: vAPI.webextFlavor.env, env: vAPI.webextFlavor.env,
}; };
const t0 = Date.now(); const t0 = Date.now();
dnrRulesetFromRawLists(listPromises, options).then(details => { dnrRulesetFromRawLists(listPromises, options).then(result => {
const { network } = result;
const replacer = (k, v) => { const replacer = (k, v) => {
if ( k.startsWith('__') ) { return; } if ( k.startsWith('__') ) { return; }
if ( Array.isArray(v) ) { if ( Array.isArray(v) ) {
@ -193,13 +196,13 @@ const onMessage = function(request, sender, callback) {
rule.action.type === 'redirect' && rule.action.type === 'redirect' &&
rule.action.redirect.transform !== undefined; rule.action.redirect.transform !== undefined;
const runtime = Date.now() - t0; const runtime = Date.now() - t0;
const { ruleset } = details; const { ruleset } = network;
const out = [ const out = [
`dnrRulesetFromRawLists(${JSON.stringify(listNames, null, 2)})`, `dnrRulesetFromRawLists(${JSON.stringify(listNames, null, 2)})`,
`Run time: ${runtime} ms`, `Run time: ${runtime} ms`,
`Filters count: ${details.filterCount}`, `Filters count: ${network.filterCount}`,
`Accepted filter count: ${details.acceptedFilterCount}`, `Accepted filter count: ${network.acceptedFilterCount}`,
`Rejected filter count: ${details.rejectedFilterCount}`, `Rejected filter count: ${network.rejectedFilterCount}`,
`Resulting DNR rule count: ${ruleset.length}`, `Resulting DNR rule count: ${ruleset.length}`,
]; ];
const good = ruleset.filter(rule => const good = ruleset.filter(rule =>
@ -237,6 +240,12 @@ const onMessage = function(request, sender, callback) {
isUnsupported(rule) isUnsupported(rule)
); );
out.push(`+ Unsupported filters (${bad.length}): ${JSON.stringify(bad, replacer, 2)}`); 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')); callback(out.join('\n'));
}); });
return; 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) { function addToDNR(context, list) {
const writer = new CompiledListWriter(); const writer = new CompiledListWriter();
const lineIter = new LineIterator( const lineIter = new LineIterator(
@ -58,7 +144,11 @@ function addToDNR(context, list) {
parser.analyze(line); parser.analyze(line);
if ( parser.shouldIgnore() ) { continue; } 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 // https://github.com/gorhill/uBlock/issues/2599
// convert hostname to punycode if needed // convert hostname to punycode if needed
@ -98,7 +188,11 @@ async function dnrRulesetFromRawLists(lists, options = {}) {
} }
} }
await Promise.all(toLoad); 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 platform/mv3/*.js $TMPDIR/
cp assets/assets.json $TMPDIR/ cp assets/assets.json $TMPDIR/
cd $TMPDIR cd $TMPDIR
node --no-warnings make-rulesets.js output=$DES quick=$QUICK node --no-warnings make-rulesets.js output=$DES
cd - > /dev/null cd - > /dev/null
rm -rf $TMPDIR rm -rf $TMPDIR
fi fi