mirror of
https://github.com/gorhill/uBlock.git
synced 2024-09-18 17:02:27 +02:00
Add element picker widget to control specificity
Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/851 The ctrl key is no longer used to adjust specificity of a candidate filter. A new widget has been added to adjust the specificity of a candidate filter to various level. The widget will be visible as long as the candidate filter matches one entry in the list of suggested candidate cosmetic filters.
This commit is contained in:
parent
016a774780
commit
1268f0ae43
@ -13,7 +13,7 @@ html#ublock0-epicker,
|
||||
outline: none;
|
||||
}
|
||||
#ublock0-epicker aside {
|
||||
background-color: #eee;
|
||||
background-color: var(--default-surface);
|
||||
border: 1px solid #aaa;
|
||||
bottom: 4px;
|
||||
box-sizing: border-box;
|
||||
@ -45,33 +45,30 @@ html#ublock0-epicker,
|
||||
margin: 0.25em 0 0 0;
|
||||
}
|
||||
#ublock0-epicker button {
|
||||
background-color: #ccc;
|
||||
border: 1px solid #aaa;
|
||||
background-color: var(--button-surface);
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: none;
|
||||
color: #000;
|
||||
color: var(--button-ink);
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
padding: 4px 6px;
|
||||
padding: 0.4em 0.8em;
|
||||
}
|
||||
#ublock0-epicker button:disabled {
|
||||
color: #999;
|
||||
background-color: #ccc;
|
||||
color: var(--button-disabled-ink);
|
||||
background-color: var(--button-disabled-surface);
|
||||
}
|
||||
#ublock0-epicker button:not(:disabled):hover {
|
||||
opacity: 1;
|
||||
background-color: var(--button-surface-hover);
|
||||
}
|
||||
#ublock0-epicker #create:not(:disabled) {
|
||||
background-color: hsl(36, 100%, 83%);
|
||||
border-color: hsl(36, 50%, 60%);
|
||||
background-color: var(--button-important-surface);
|
||||
}
|
||||
#ublock0-epicker #preview {
|
||||
float: left;
|
||||
#ublock0-epicker #create:not(:disabled):hover {
|
||||
background-color: var(--button-important-surface-hover);
|
||||
}
|
||||
#ublock0-epicker.preview #preview {
|
||||
background-color: hsl(204, 100%, 83%);
|
||||
border-color: hsl(204, 50%, 60%);
|
||||
background-color: var(--button-active-surface);
|
||||
}
|
||||
#ublock0-epicker section {
|
||||
border: 0;
|
||||
@ -87,7 +84,7 @@ html#ublock0-epicker,
|
||||
#ublock0-epicker section.invalidFilter > div:first-child {
|
||||
border-color: red;
|
||||
}
|
||||
#ublock0-epicker section > div:first-child > textarea {
|
||||
#ublock0-epicker section textarea {
|
||||
background-color: #fff;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
@ -102,14 +99,93 @@ html#ublock0-epicker,
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
}
|
||||
#ublock0-epicker #resultsetCount {
|
||||
background-color: #aaa;
|
||||
#ublock0-epicker section textarea + div {
|
||||
background-color: transparent;
|
||||
bottom: 0;
|
||||
color: white;
|
||||
padding: 2px 4px;
|
||||
display: flex;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
#resultsetModifiers {
|
||||
align-items: flex-end;
|
||||
display: inline-flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
#resultsetSpecificity {
|
||||
display: inline-flex;
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
}
|
||||
#resultsetSpecificity.hide {
|
||||
display: none;
|
||||
}
|
||||
#resultsetSpecificity [data-specificity] {
|
||||
background-color: var(--button-surface);
|
||||
border: 0;
|
||||
border-left: 1px solid white;
|
||||
display: inline-block;
|
||||
height: 1.2em;
|
||||
width: 1.5em;
|
||||
}
|
||||
#resultsetSpecificity[data-specificity="0"] [data-specificity="0"],
|
||||
#resultsetSpecificity[data-specificity="1"] [data-specificity="0"],
|
||||
#resultsetSpecificity[data-specificity="1"] [data-specificity="1"],
|
||||
#resultsetSpecificity[data-specificity="2"] [data-specificity="0"],
|
||||
#resultsetSpecificity[data-specificity="2"] [data-specificity="1"],
|
||||
#resultsetSpecificity[data-specificity="2"] [data-specificity="2"],
|
||||
#resultsetSpecificity[data-specificity="3"] [data-specificity="0"],
|
||||
#resultsetSpecificity[data-specificity="3"] [data-specificity="1"],
|
||||
#resultsetSpecificity[data-specificity="3"] [data-specificity="2"],
|
||||
#resultsetSpecificity[data-specificity="3"] [data-specificity="3"],
|
||||
#resultsetSpecificity[data-specificity="4"] [data-specificity="0"],
|
||||
#resultsetSpecificity[data-specificity="4"] [data-specificity="1"],
|
||||
#resultsetSpecificity[data-specificity="4"] [data-specificity="2"],
|
||||
#resultsetSpecificity[data-specificity="4"] [data-specificity="3"],
|
||||
#resultsetSpecificity[data-specificity="4"] [data-specificity="4"],
|
||||
#resultsetSpecificity[data-specificity="5"] [data-specificity="0"],
|
||||
#resultsetSpecificity[data-specificity="5"] [data-specificity="1"],
|
||||
#resultsetSpecificity[data-specificity="5"] [data-specificity="2"],
|
||||
#resultsetSpecificity[data-specificity="5"] [data-specificity="3"],
|
||||
#resultsetSpecificity[data-specificity="5"] [data-specificity="4"],
|
||||
#resultsetSpecificity[data-specificity="5"] [data-specificity="5"],
|
||||
#resultsetSpecificity[data-specificity="6"] [data-specificity="0"],
|
||||
#resultsetSpecificity[data-specificity="6"] [data-specificity="1"],
|
||||
#resultsetSpecificity[data-specificity="6"] [data-specificity="2"],
|
||||
#resultsetSpecificity[data-specificity="6"] [data-specificity="3"],
|
||||
#resultsetSpecificity[data-specificity="6"] [data-specificity="4"],
|
||||
#resultsetSpecificity[data-specificity="6"] [data-specificity="5"],
|
||||
#resultsetSpecificity[data-specificity="6"] [data-specificity="6"],
|
||||
#resultsetSpecificity[data-specificity="7"] [data-specificity="0"],
|
||||
#resultsetSpecificity[data-specificity="7"] [data-specificity="1"],
|
||||
#resultsetSpecificity[data-specificity="7"] [data-specificity="2"],
|
||||
#resultsetSpecificity[data-specificity="7"] [data-specificity="3"],
|
||||
#resultsetSpecificity[data-specificity="7"] [data-specificity="4"],
|
||||
#resultsetSpecificity[data-specificity="7"] [data-specificity="5"],
|
||||
#resultsetSpecificity[data-specificity="7"] [data-specificity="6"],
|
||||
#resultsetSpecificity[data-specificity="7"] [data-specificity="7"] {
|
||||
background-color: var(--button-active-surface);
|
||||
}
|
||||
#resultsetSpecificity input {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
#resultsetCount {
|
||||
background-color: #aaa;
|
||||
color: white;
|
||||
min-width: 2.2em;
|
||||
padding: 2px 0;
|
||||
text-align: center;
|
||||
}
|
||||
#ublock0-epicker section.invalidFilter #resultsetCount {
|
||||
background-color: red;
|
||||
}
|
||||
|
@ -58,6 +58,11 @@ let filterHostname = '';
|
||||
let filterOrigin = '';
|
||||
let resultsetOpt;
|
||||
|
||||
let netFilterCandidates = [];
|
||||
let cosmeticFilterCandidates = [];
|
||||
let computedCandidateSlot = 0;
|
||||
let computedCandidate = '';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const filterFromTextarea = function() {
|
||||
@ -110,6 +115,9 @@ const candidateFromFilterChoice = function(filterChoice) {
|
||||
elem.classList.remove('active');
|
||||
}
|
||||
|
||||
computedCandidateSlot = slot;
|
||||
computedCandidate = '';
|
||||
|
||||
if ( filter === undefined ) { return ''; }
|
||||
|
||||
// For net filters there no such thing as a path
|
||||
@ -142,35 +150,82 @@ const candidateFromFilterChoice = function(filterChoice) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
// TODO: Maybe add another step to remove attribute values?
|
||||
const specificity = [
|
||||
0b0000, // remove hierarchy; remove id, nth-of-type, attribute values
|
||||
0b0010, // remove hierarchy; remove id, nth-of-type
|
||||
0b0011, // remove hierarchy
|
||||
0b1000, // trim hierarchy; remove id, nth-of-type, attribute values
|
||||
0b1010, // trim hierarchy; remove id, nth-of-type
|
||||
0b1100, // remove id, nth-of-type, attribute values
|
||||
0b1110, // remove id, nth-of-type
|
||||
0b1111, // keep all = most specific
|
||||
][
|
||||
parseInt(
|
||||
$id('resultsetSpecificity').getAttribute('data-specificity'),
|
||||
10
|
||||
)
|
||||
];
|
||||
|
||||
// Return path: the target element, then all siblings prepended
|
||||
let selector = '', joiner = '';
|
||||
for ( ; slot < filters.length; slot++ ) {
|
||||
filter = filters[slot];
|
||||
const paths = [];
|
||||
for ( let i = slot; i < filters.length; i++ ) {
|
||||
filter = filters[i].slice(2);
|
||||
// Remove id, nth-of-type
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/162
|
||||
// Mind escaped periods: they do not denote a class identifier.
|
||||
if ( (specificity & 0b0001) === 0 ) {
|
||||
filter = filter.replace(/:nth-of-type\(\d+\)/, '');
|
||||
if (
|
||||
filter.charAt(0) === '#' && (
|
||||
(specificity & 0b1000) === 0 || i === slot
|
||||
)
|
||||
) {
|
||||
const pos = filter.search(/[^\\]\./);
|
||||
if ( pos !== -1 ) {
|
||||
filter = filter.slice(pos + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove attribute values.
|
||||
if ( (specificity & 0b0010) === 0 ) {
|
||||
const match = /^\[([^^=]+)\^?=.+\]$/.exec(filter);
|
||||
if ( match !== null ) {
|
||||
filter = `[${match[1]}]`;
|
||||
}
|
||||
}
|
||||
// Remove all classes when an id exists.
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/162
|
||||
// Mind escaped periods: they do not denote a class identifier.
|
||||
if ( filter.charAt(2) === '#' ) {
|
||||
if ( filter.charAt(0) === '#' ) {
|
||||
filter = filter.replace(/([^\\])\..+$/, '$1');
|
||||
}
|
||||
selector = filter.slice(2) + joiner + selector;
|
||||
if ( paths.length !== 0 ) {
|
||||
filter += ' > ';
|
||||
}
|
||||
paths.unshift(filter);
|
||||
// Stop at any element with an id: these are unique in a web page
|
||||
if ( filter.startsWith('###') ) { break; }
|
||||
// Stop if current selector matches only one element on the page
|
||||
if ( document.querySelectorAll(selector).length === 1 ) { break; }
|
||||
joiner = ' > ';
|
||||
if ( (specificity & 0b1000) === 0 || filter.startsWith('#') ) { break; }
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/2519
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/17
|
||||
if (
|
||||
slot === filters.length &&
|
||||
selector.startsWith('body > ') === false &&
|
||||
document.querySelectorAll(selector).length > 1
|
||||
) {
|
||||
selector = 'body > ' + selector;
|
||||
// Trim hierarchy: remove generic elements from path
|
||||
if ( (specificity & 0b1100) === 0b1000 ) {
|
||||
let i = 0;
|
||||
while ( i < paths.length - 1 ) {
|
||||
if ( /^[a-z0-9]+ > $/.test(paths[i+1]) ) {
|
||||
if ( paths[i].endsWith(' > ') ) {
|
||||
paths[i] = paths[i].slice(0, -2);
|
||||
}
|
||||
paths.splice(i + 1, 1);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '##' + selector;
|
||||
computedCandidate = `##${paths.join('')}`;
|
||||
|
||||
return computedCandidate;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
@ -305,6 +360,10 @@ const onCandidateChanged = function() {
|
||||
$id('resultsetCount').textContent = 'E';
|
||||
$id('create').setAttribute('disabled', '');
|
||||
}
|
||||
$id('resultsetSpecificity').classList.toggle(
|
||||
'hide',
|
||||
taCandidate.value === '' || taCandidate.value !== computedCandidate
|
||||
);
|
||||
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
||||
what: 'dialogSetFilter',
|
||||
filter,
|
||||
@ -362,6 +421,20 @@ const onQuitClicked = function() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onSpecificityChanged = function(ev) {
|
||||
const { target } = ev;
|
||||
$id('resultsetSpecificity').setAttribute('data-specificity', target.value);
|
||||
if ( taCandidate.value === computedCandidate ) {
|
||||
taCandidate.value = candidateFromFilterChoice({
|
||||
filters: cosmeticFilterCandidates,
|
||||
slot: computedCandidateSlot,
|
||||
});
|
||||
onCandidateChanged();
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onCandidateClicked = function(ev) {
|
||||
let li = ev.target.closest('li');
|
||||
if ( li === null ) { return; }
|
||||
@ -370,7 +443,6 @@ const onCandidateClicked = function(ev) {
|
||||
const choice = {
|
||||
filters: Array.from(ul.querySelectorAll('li')).map(a => a.textContent),
|
||||
slot: 0,
|
||||
broad: ev.ctrlKey || ev.metaKey
|
||||
};
|
||||
while ( li.previousElementSibling !== null ) {
|
||||
li = li.previousElementSibling;
|
||||
@ -511,10 +583,37 @@ const eatEvent = function(ev) {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Create lists of candidate filters. This takes into account whether the
|
||||
// current mode is narrow or broad.
|
||||
|
||||
const populateCandidates = function(candidates, selector) {
|
||||
|
||||
const root = dialog.querySelector(selector);
|
||||
const ul = root.querySelector('ul');
|
||||
while ( ul.firstChild !== null ) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
for ( let i = 0; i < candidates.length; i++ ) {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = candidates[i];
|
||||
ul.appendChild(li);
|
||||
}
|
||||
if ( candidates.length !== 0 ) {
|
||||
root.style.removeProperty('display');
|
||||
} else {
|
||||
root.style.setProperty('display', 'none', 'important');
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const showDialog = function(details) {
|
||||
pausePicker();
|
||||
|
||||
const { netFilters, cosmeticFilters, filter, options = {} } = details;
|
||||
const { netFilters, cosmeticFilters, filter } = details;
|
||||
|
||||
netFilterCandidates = netFilters;
|
||||
cosmeticFilterCandidates = cosmeticFilters;
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/738
|
||||
// Trim dots.
|
||||
@ -524,27 +623,8 @@ const showDialog = function(details) {
|
||||
}
|
||||
filterOrigin = details.origin;
|
||||
|
||||
// Create lists of candidate filters
|
||||
const populate = function(src, des) {
|
||||
const root = dialog.querySelector(des);
|
||||
const ul = root.querySelector('ul');
|
||||
while ( ul.firstChild !== null ) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
for ( let i = 0; i < src.length; i++ ) {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = src[i];
|
||||
ul.appendChild(li);
|
||||
}
|
||||
if ( src.length !== 0 ) {
|
||||
root.style.removeProperty('display');
|
||||
} else {
|
||||
root.style.setProperty('display', 'none', 'important');
|
||||
}
|
||||
};
|
||||
|
||||
populate(netFilters, '#netFilters');
|
||||
populate(cosmeticFilters, '#cosmeticFilters');
|
||||
populateCandidates(netFilters, '#netFilters');
|
||||
populateCandidates(cosmeticFilters, '#cosmeticFilters');
|
||||
|
||||
dialog.querySelector('ul').style.display =
|
||||
netFilters.length || cosmeticFilters.length ? '' : 'none';
|
||||
@ -566,7 +646,6 @@ const showDialog = function(details) {
|
||||
const filterChoice = {
|
||||
filters: filter.filters,
|
||||
slot: filter.slot,
|
||||
broad: options.broad || false
|
||||
};
|
||||
|
||||
taCandidate.value = candidateFromFilterChoice(filterChoice);
|
||||
@ -609,9 +688,10 @@ const startPicker = function() {
|
||||
$id('create').addEventListener('click', onCreateClicked);
|
||||
$id('pick').addEventListener('click', onPickClicked);
|
||||
$id('quit').addEventListener('click', onQuitClicked);
|
||||
$id('candidateFilters').addEventListener('click', onCandidateClicked);
|
||||
$id('toolbar').addEventListener('mousedown', onStartMoving);
|
||||
$id('toolbar').addEventListener('touchstart', onStartMoving);
|
||||
$id('candidateFilters').addEventListener('click', onCandidateClicked);
|
||||
$stor('#resultsetSpecificity input').addEventListener('input', onSpecificityChanged);
|
||||
staticFilteringParser = new vAPI.StaticFilteringParser({ interactive: true });
|
||||
};
|
||||
|
||||
|
@ -517,6 +517,9 @@ const filtersFrom = function(x, y) {
|
||||
}
|
||||
|
||||
// Cosmetic filter candidates from ancestors.
|
||||
// https://github.com/gorhill/uBlock/issues/2519
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/17
|
||||
// Prepend `body` if full selector is ambiguous.
|
||||
let elem = first;
|
||||
while ( elem && elem !== document.body ) {
|
||||
cosmeticFilterFromElement(elem);
|
||||
@ -526,10 +529,11 @@ const filtersFrom = function(x, y) {
|
||||
// uses `nth-of-type`.
|
||||
let i = cosmeticFilterCandidates.length;
|
||||
if ( i !== 0 ) {
|
||||
let selector = cosmeticFilterCandidates[i-1];
|
||||
const selector = cosmeticFilterCandidates[i-1];
|
||||
if (
|
||||
selector.indexOf(':nth-of-type(') !== -1 &&
|
||||
safeQuerySelectorAll(document.body, selector).length > 1
|
||||
safeQuerySelectorAll(document.body, selector).length > 1 ||
|
||||
safeQuerySelectorAll(document, cosmeticFilterCandidates.join(' > ')).length > 1
|
||||
) {
|
||||
cosmeticFilterCandidates.push('##body');
|
||||
}
|
||||
@ -1079,6 +1083,9 @@ const onDialogMessage = function(msg) {
|
||||
break;
|
||||
case 'togglePreview':
|
||||
filterToDOMInterface.preview(msg.state);
|
||||
if ( msg.state === false ) {
|
||||
highlightElements(targetElements, true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -4,6 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>uBlock Origin Element Picker</title>
|
||||
<link rel="stylesheet" href="../css/themes/default.css">
|
||||
<link rel="stylesheet" href="../css/epicker-ui.css">
|
||||
</head>
|
||||
|
||||
@ -12,7 +13,20 @@
|
||||
<section>
|
||||
<div>
|
||||
<textarea lang="en" dir="ltr" spellcheck="false"></textarea>
|
||||
<div id="resultsetCount"></div>
|
||||
<div>
|
||||
<span id="resultsetModifiers">
|
||||
<span id="resultsetSpecificity" data-specificity="6">
|
||||
<span data-specificity="0"></span>
|
||||
<span data-specificity="1"></span>
|
||||
<span data-specificity="2"></span>
|
||||
<span data-specificity="3"></span>
|
||||
<span data-specificity="4"></span>
|
||||
<span data-specificity="5"></span>
|
||||
<span data-specificity="6"></span>
|
||||
<span data-specificity="7"></span>
|
||||
<input type="range" min="0" max="7" value="6"></span>
|
||||
</span>
|
||||
<span id="resultsetCount"></span></div>
|
||||
</div>
|
||||
<div id="toolbar">
|
||||
<div>
|
||||
@ -27,10 +41,11 @@
|
||||
</section>
|
||||
<ul id="candidateFilters">
|
||||
<li id="netFilters">
|
||||
<span data-i18n="pickerNetFilters"></span><ul lang="en" class="changeFilter"></ul>
|
||||
<span data-i18n="pickerNetFilters"></span>
|
||||
<ul lang="en" class="changeFilter"></ul>
|
||||
</li>
|
||||
<li id="cosmeticFilters">
|
||||
<span data-i18n="pickerCosmeticFilters"></span> <span data-i18n="pickerCosmeticFiltersHint"></span>
|
||||
<li id="cosmeticFilters" data-specificity="3">
|
||||
<span data-i18n="pickerCosmeticFilters"></span>
|
||||
<ul lang="en" class="changeFilter"></ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
Loading…
Reference in New Issue
Block a user