2020-09-01 18:32:12 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
uBlock Origin - a browser extension to block requests.
|
|
|
|
Copyright (C) 2014-present Raymond Hill
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
|
|
|
|
Home: https://github.com/gorhill/uBlock
|
|
|
|
*/
|
|
|
|
|
2020-10-14 16:21:30 +02:00
|
|
|
/* global CodeMirror */
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
'use strict';
|
|
|
|
|
2021-07-25 16:55:35 +02:00
|
|
|
import './codemirror/ubo-static-filtering.js';
|
|
|
|
|
|
|
|
import { hostnameFromURI } from './uri-utils.js';
|
2023-01-23 22:53:18 +01:00
|
|
|
import * as sfp from './static-filtering-parser.js';
|
2021-07-25 16:55:35 +02:00
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
(( ) => {
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
if ( typeof vAPI !== 'object' ) { return; }
|
|
|
|
|
2020-09-03 16:27:35 +02:00
|
|
|
const $id = id => document.getElementById(id);
|
|
|
|
const $stor = selector => document.querySelector(selector);
|
|
|
|
const $storAll = selector => document.querySelectorAll(selector);
|
|
|
|
|
|
|
|
const pickerRoot = document.documentElement;
|
|
|
|
const dialog = $stor('aside');
|
|
|
|
let staticFilteringParser;
|
|
|
|
|
|
|
|
const svgRoot = $stor('svg');
|
|
|
|
const svgOcean = svgRoot.children[0];
|
|
|
|
const svgIslands = svgRoot.children[1];
|
|
|
|
const NoPaths = 'M0 0';
|
|
|
|
|
2020-11-29 17:31:20 +01:00
|
|
|
const reCosmeticAnchor = /^#[$?]?#/;
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
const epickerId = (( ) => {
|
|
|
|
const url = new URL(self.location.href);
|
2020-09-03 16:27:35 +02:00
|
|
|
if ( url.searchParams.has('zap') ) {
|
|
|
|
pickerRoot.classList.add('zap');
|
|
|
|
}
|
2020-09-01 18:32:12 +02:00
|
|
|
return url.searchParams.get('epid');
|
|
|
|
})();
|
|
|
|
if ( epickerId === null ) { return; }
|
|
|
|
|
2020-10-07 17:52:38 +02:00
|
|
|
const docURL = new URL(vAPI.getURL(''));
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
let epickerConnectionId;
|
2020-09-07 14:28:01 +02:00
|
|
|
let resultsetOpt;
|
2020-09-01 18:32:12 +02:00
|
|
|
|
2020-09-09 15:27:53 +02:00
|
|
|
let netFilterCandidates = [];
|
|
|
|
let cosmeticFilterCandidates = [];
|
|
|
|
let computedCandidateSlot = 0;
|
|
|
|
let computedCandidate = '';
|
2020-10-20 11:37:07 +02:00
|
|
|
const computedSpecificityCandidates = new Map();
|
2020-09-10 16:32:53 +02:00
|
|
|
let needBody = false;
|
2020-09-09 15:27:53 +02:00
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-10-14 16:21:30 +02:00
|
|
|
const cmEditor = new CodeMirror(document.querySelector('.codeMirrorContainer'), {
|
|
|
|
autoCloseBrackets: true,
|
|
|
|
autofocus: true,
|
|
|
|
extraKeys: {
|
|
|
|
'Ctrl-Space': 'autocomplete',
|
|
|
|
},
|
|
|
|
lineWrapping: true,
|
|
|
|
matchBrackets: true,
|
|
|
|
maxScanLines: 1,
|
|
|
|
});
|
|
|
|
|
|
|
|
vAPI.messaging.send('dashboard', {
|
|
|
|
what: 'getAutoCompleteDetails'
|
|
|
|
}).then(response => {
|
2020-12-15 15:38:20 +01:00
|
|
|
// For unknown reasons, `instanceof Object` does not work here in Firefox.
|
|
|
|
if ( typeof response !== 'object' ) { return; }
|
2020-10-14 16:21:30 +02:00
|
|
|
const mode = cmEditor.getMode();
|
|
|
|
if ( mode.setHints instanceof Function ) {
|
|
|
|
mode.setHints(response);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const rawFilterFromTextarea = function() {
|
|
|
|
const text = cmEditor.getValue();
|
|
|
|
const pos = text.indexOf('\n');
|
|
|
|
return pos === -1 ? text : text.slice(0, pos);
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
const filterFromTextarea = function() {
|
2020-10-14 16:21:30 +02:00
|
|
|
const filter = rawFilterFromTextarea();
|
|
|
|
if ( filter === '' ) { return ''; }
|
2023-01-23 22:53:18 +01:00
|
|
|
const parser = staticFilteringParser;
|
|
|
|
parser.parse(filter);
|
|
|
|
if ( parser.isFilter() === false ) { return '!'; }
|
|
|
|
if ( parser.isExtendedFilter() ) {
|
|
|
|
if ( parser.isCosmeticFilter() === false ) { return '!'; }
|
|
|
|
} else if ( parser.isNetworkFilter() === false ) {
|
2020-10-08 15:49:35 +02:00
|
|
|
return '!';
|
|
|
|
}
|
|
|
|
return filter;
|
2020-09-01 18:32:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-10 16:32:53 +02:00
|
|
|
const renderRange = function(id, value, invert = false) {
|
|
|
|
const input = $stor(`#${id} input`);
|
|
|
|
const max = parseInt(input.max, 10);
|
|
|
|
if ( typeof value !== 'number' ) {
|
|
|
|
value = parseInt(input.value, 10);
|
|
|
|
}
|
|
|
|
if ( invert ) {
|
|
|
|
value = max - value;
|
|
|
|
}
|
|
|
|
input.value = value;
|
2020-09-12 13:28:46 +02:00
|
|
|
const slider = $stor(`#${id} > span`);
|
|
|
|
const lside = slider.children[0];
|
|
|
|
const thumb = slider.children[1];
|
2020-09-12 14:26:44 +02:00
|
|
|
const sliderWidth = slider.offsetWidth;
|
|
|
|
const maxPercent = (sliderWidth - thumb.offsetWidth) / sliderWidth * 100;
|
|
|
|
const widthPercent = value / max * maxPercent;
|
|
|
|
lside.style.width = `${widthPercent}%`;
|
2020-09-10 16:32:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
const userFilterFromCandidate = function(filter) {
|
|
|
|
if ( filter === '' || filter === '!' ) { return; }
|
|
|
|
|
2021-07-25 16:55:35 +02:00
|
|
|
const hn = hostnameFromURI(docURL.href);
|
2020-10-07 17:52:38 +02:00
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
// Cosmetic filter?
|
2020-11-29 17:31:20 +01:00
|
|
|
if ( reCosmeticAnchor.test(filter) ) {
|
2020-10-07 17:52:38 +02:00
|
|
|
return hn + filter;
|
2020-09-01 18:32:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Assume net filter
|
|
|
|
const opts = [];
|
|
|
|
|
|
|
|
// If no domain included in filter, we need domain option
|
|
|
|
if ( filter.startsWith('||') === false ) {
|
2020-10-07 17:52:38 +02:00
|
|
|
opts.push(`domain=${hn}`);
|
2020-09-01 18:32:12 +02:00
|
|
|
}
|
|
|
|
|
2020-09-07 14:28:01 +02:00
|
|
|
if ( resultsetOpt !== undefined ) {
|
|
|
|
opts.push(resultsetOpt);
|
2020-09-01 18:32:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( opts.length ) {
|
|
|
|
filter += '$' + opts.join(',');
|
|
|
|
}
|
|
|
|
|
|
|
|
return filter;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const candidateFromFilterChoice = function(filterChoice) {
|
|
|
|
let { slot, filters } = filterChoice;
|
|
|
|
let filter = filters[slot];
|
|
|
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/47
|
|
|
|
for ( const elem of $storAll('#candidateFilters li') ) {
|
|
|
|
elem.classList.remove('active');
|
|
|
|
}
|
|
|
|
|
2020-09-09 15:27:53 +02:00
|
|
|
computedCandidateSlot = slot;
|
|
|
|
computedCandidate = '';
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
if ( filter === undefined ) { return ''; }
|
|
|
|
|
|
|
|
// For net filters there no such thing as a path
|
|
|
|
if ( filter.startsWith('##') === false ) {
|
|
|
|
$stor(`#netFilters li:nth-of-type(${slot+1})`)
|
|
|
|
.classList.add('active');
|
|
|
|
return filter;
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, we have a cosmetic filter
|
|
|
|
|
|
|
|
$stor(`#cosmeticFilters li:nth-of-type(${slot+1})`)
|
|
|
|
.classList.add('active');
|
|
|
|
|
2020-10-16 23:12:22 +02:00
|
|
|
return cosmeticCandidatesFromFilterChoice(filterChoice);
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const cosmeticCandidatesFromFilterChoice = function(filterChoice) {
|
2020-10-20 11:37:07 +02:00
|
|
|
let { slot, filters } = filterChoice;
|
|
|
|
|
|
|
|
renderRange('resultsetDepth', slot, true);
|
|
|
|
renderRange('resultsetSpecificity');
|
|
|
|
|
|
|
|
if ( computedSpecificityCandidates.has(slot) ) {
|
|
|
|
onCandidatesOptimized({ slot });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-16 23:12:22 +02:00
|
|
|
const specificities = [
|
2020-09-09 15:27:53 +02:00
|
|
|
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
|
2020-10-16 23:12:22 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
const candidates = [];
|
|
|
|
|
|
|
|
let filter = filters[slot];
|
|
|
|
|
|
|
|
for ( const specificity of specificities ) {
|
|
|
|
// Return path: the target element, then all siblings prepended
|
|
|
|
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);
|
|
|
|
}
|
2020-09-09 15:27:53 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-16 23:12:22 +02:00
|
|
|
// Remove attribute values.
|
|
|
|
if ( (specificity & 0b0010) === 0 ) {
|
2021-03-31 18:21:29 +02:00
|
|
|
const match = /^\[([^^*$=]+)[\^*$]?=.+\]$/.exec(filter);
|
2020-10-16 23:12:22 +02:00
|
|
|
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(0) === '#' ) {
|
|
|
|
filter = filter.replace(/([^\\])\..+$/, '$1');
|
|
|
|
}
|
|
|
|
if ( paths.length !== 0 ) {
|
|
|
|
filter += ' > ';
|
|
|
|
}
|
|
|
|
paths.unshift(filter);
|
|
|
|
// Stop at any element with an id: these are unique in a web page
|
|
|
|
if ( (specificity & 0b1000) === 0 || filter.startsWith('#') ) {
|
|
|
|
break;
|
2020-09-09 15:27:53 +02:00
|
|
|
}
|
|
|
|
}
|
2020-09-01 18:32:12 +02:00
|
|
|
|
2020-10-16 23:12:22 +02:00
|
|
|
// 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;
|
2020-09-09 15:27:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-01 18:32:12 +02:00
|
|
|
|
2020-10-16 23:12:22 +02:00
|
|
|
if (
|
|
|
|
needBody &&
|
|
|
|
paths.length !== 0 &&
|
|
|
|
paths[0].startsWith('#') === false &&
|
2020-11-11 15:39:07 +01:00
|
|
|
paths[0].startsWith('body ') === false &&
|
2020-10-16 23:12:22 +02:00
|
|
|
(specificity & 0b1100) !== 0
|
|
|
|
) {
|
|
|
|
paths.unshift('body > ');
|
|
|
|
}
|
2020-09-10 16:32:53 +02:00
|
|
|
|
2020-10-16 23:12:22 +02:00
|
|
|
candidates.push(paths);
|
|
|
|
}
|
2020-09-09 15:27:53 +02:00
|
|
|
|
2020-10-06 23:26:28 +02:00
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
2020-10-16 23:12:22 +02:00
|
|
|
what: 'optimizeCandidates',
|
|
|
|
candidates,
|
2020-10-20 11:37:07 +02:00
|
|
|
slot,
|
2020-10-06 23:26:28 +02:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-10-16 23:12:22 +02:00
|
|
|
const onCandidatesOptimized = function(details) {
|
2020-10-06 23:26:28 +02:00
|
|
|
$id('resultsetModifiers').classList.remove('hide');
|
2020-10-16 23:12:22 +02:00
|
|
|
const i = parseInt($stor('#resultsetSpecificity input').value, 10);
|
2020-10-20 11:37:07 +02:00
|
|
|
if ( Array.isArray(details.candidates) ) {
|
|
|
|
computedSpecificityCandidates.set(details.slot, details.candidates);
|
|
|
|
}
|
|
|
|
const candidates = computedSpecificityCandidates.get(details.slot);
|
|
|
|
computedCandidate = candidates[i];
|
2020-10-14 16:21:30 +02:00
|
|
|
cmEditor.setValue(computedCandidate);
|
|
|
|
cmEditor.clearHistory();
|
2020-10-06 23:26:28 +02:00
|
|
|
onCandidateChanged();
|
2020-09-01 18:32:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-03 16:27:35 +02:00
|
|
|
const onSvgClicked = function(ev) {
|
|
|
|
// If zap mode, highlight element under mouse, this makes the zapper usable
|
|
|
|
// on touch screens.
|
|
|
|
if ( pickerRoot.classList.contains('zap') ) {
|
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
|
|
|
what: 'zapElementAtPoint',
|
|
|
|
mx: ev.clientX,
|
|
|
|
my: ev.clientY,
|
|
|
|
options: {
|
|
|
|
stay: ev.shiftKey || ev.type === 'touch',
|
|
|
|
highlight: ev.target !== svgIslands,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/810#issuecomment-74600694
|
|
|
|
// Unpause picker if:
|
|
|
|
// - click outside dialog AND
|
|
|
|
// - not in preview mode
|
|
|
|
if ( pickerRoot.classList.contains('paused') ) {
|
|
|
|
if ( pickerRoot.classList.contains('preview') === false ) {
|
|
|
|
unpausePicker();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Force dialog to always be visible when using a touch-driven device.
|
|
|
|
if ( ev.type === 'touch' ) {
|
|
|
|
pickerRoot.classList.add('show');
|
|
|
|
}
|
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
|
|
|
what: 'filterElementAtPoint',
|
|
|
|
mx: ev.clientX,
|
|
|
|
my: ev.clientY,
|
|
|
|
broad: ev.ctrlKey,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
Swipe right:
|
|
|
|
If picker not paused: quit picker
|
|
|
|
If picker paused and dialog visible: hide dialog
|
|
|
|
If picker paused and dialog not visible: quit picker
|
|
|
|
|
|
|
|
Swipe left:
|
|
|
|
If picker paused and dialog not visible: show dialog
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
const onSvgTouch = (( ) => {
|
|
|
|
let startX = 0, startY = 0;
|
|
|
|
let t0 = 0;
|
|
|
|
return ev => {
|
|
|
|
if ( ev.type === 'touchstart' ) {
|
|
|
|
startX = ev.touches[0].screenX;
|
|
|
|
startY = ev.touches[0].screenY;
|
|
|
|
t0 = ev.timeStamp;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( startX === undefined ) { return; }
|
|
|
|
const stopX = ev.changedTouches[0].screenX;
|
|
|
|
const stopY = ev.changedTouches[0].screenY;
|
|
|
|
const angle = Math.abs(Math.atan2(stopY - startY, stopX - startX));
|
|
|
|
const distance = Math.sqrt(
|
|
|
|
Math.pow(stopX - startX, 2),
|
|
|
|
Math.pow(stopY - startY, 2)
|
|
|
|
);
|
|
|
|
// Interpret touch events as a tap if:
|
|
|
|
// - Swipe is not valid; and
|
|
|
|
// - The time between start and stop was less than 200ms.
|
|
|
|
const duration = ev.timeStamp - t0;
|
|
|
|
if ( distance < 32 && duration < 200 ) {
|
|
|
|
onSvgClicked({
|
|
|
|
type: 'touch',
|
|
|
|
target: ev.target,
|
|
|
|
clientX: ev.changedTouches[0].pageX,
|
|
|
|
clientY: ev.changedTouches[0].pageY,
|
|
|
|
});
|
|
|
|
ev.preventDefault();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( distance < 64 ) { return; }
|
|
|
|
const angleUpperBound = Math.PI * 0.25 * 0.5;
|
|
|
|
const swipeRight = angle < angleUpperBound;
|
|
|
|
if ( swipeRight === false && angle < Math.PI - angleUpperBound ) {
|
|
|
|
return;
|
|
|
|
}
|
2020-09-12 17:29:39 +02:00
|
|
|
if ( ev.cancelable ) {
|
|
|
|
ev.preventDefault();
|
|
|
|
}
|
2020-09-03 16:27:35 +02:00
|
|
|
// Swipe left.
|
|
|
|
if ( swipeRight === false ) {
|
|
|
|
if ( pickerRoot.classList.contains('paused') ) {
|
|
|
|
pickerRoot.classList.remove('hide');
|
|
|
|
pickerRoot.classList.add('show');
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Swipe right.
|
|
|
|
if (
|
|
|
|
pickerRoot.classList.contains('zap') &&
|
|
|
|
svgIslands.getAttribute('d') !== NoPaths
|
|
|
|
) {
|
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
|
|
|
what: 'unhighlight'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (
|
|
|
|
pickerRoot.classList.contains('paused') &&
|
|
|
|
pickerRoot.classList.contains('show')
|
|
|
|
) {
|
|
|
|
pickerRoot.classList.remove('show');
|
|
|
|
pickerRoot.classList.add('hide');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
quitPicker();
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
const onCandidateChanged = function() {
|
|
|
|
const filter = filterFromTextarea();
|
|
|
|
const bad = filter === '!';
|
|
|
|
$stor('section').classList.toggle('invalidFilter', bad);
|
|
|
|
if ( bad ) {
|
|
|
|
$id('resultsetCount').textContent = 'E';
|
|
|
|
$id('create').setAttribute('disabled', '');
|
|
|
|
}
|
2020-10-14 16:21:30 +02:00
|
|
|
const text = rawFilterFromTextarea();
|
2020-09-10 16:32:53 +02:00
|
|
|
$id('resultsetModifiers').classList.toggle(
|
2020-10-14 16:21:30 +02:00
|
|
|
'hide', text === '' || text !== computedCandidate
|
2020-09-09 15:27:53 +02:00
|
|
|
);
|
2020-09-01 18:32:12 +02:00
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
|
|
|
what: 'dialogSetFilter',
|
|
|
|
filter,
|
2020-11-29 17:31:20 +01:00
|
|
|
compiled: reCosmeticAnchor.test(filter)
|
2020-09-01 18:32:12 +02:00
|
|
|
? staticFilteringParser.result.compiled
|
|
|
|
: undefined,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const onPreviewClicked = function() {
|
2020-09-03 16:27:35 +02:00
|
|
|
const state = pickerRoot.classList.toggle('preview');
|
2020-09-01 18:32:12 +02:00
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
2020-09-03 16:27:35 +02:00
|
|
|
what: 'togglePreview',
|
2020-09-01 18:32:12 +02:00
|
|
|
state,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const onCreateClicked = function() {
|
|
|
|
const candidate = filterFromTextarea();
|
|
|
|
const filter = userFilterFromCandidate(candidate);
|
|
|
|
if ( filter !== undefined ) {
|
|
|
|
vAPI.messaging.send('elementPicker', {
|
|
|
|
what: 'createUserFilter',
|
|
|
|
autoComment: true,
|
|
|
|
filters: filter,
|
2020-10-07 17:52:38 +02:00
|
|
|
docURL: docURL.href,
|
2020-11-29 17:31:20 +01:00
|
|
|
killCache: reCosmeticAnchor.test(candidate) === false,
|
2020-09-01 18:32:12 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
|
|
|
what: 'dialogCreate',
|
|
|
|
filter: candidate,
|
2020-11-29 17:31:20 +01:00
|
|
|
compiled: reCosmeticAnchor.test(candidate)
|
2020-09-01 18:32:12 +02:00
|
|
|
? staticFilteringParser.result.compiled
|
|
|
|
: undefined,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-03 16:27:35 +02:00
|
|
|
const onPickClicked = function() {
|
|
|
|
unpausePicker();
|
2020-09-01 18:32:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const onQuitClicked = function() {
|
2020-09-03 16:27:35 +02:00
|
|
|
quitPicker();
|
2020-09-01 18:32:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-10 16:32:53 +02:00
|
|
|
const onDepthChanged = function() {
|
|
|
|
const input = $stor('#resultsetDepth input');
|
|
|
|
const max = parseInt(input.max, 10);
|
|
|
|
const value = parseInt(input.value, 10);
|
2020-10-06 23:26:28 +02:00
|
|
|
const text = candidateFromFilterChoice({
|
2020-09-10 16:32:53 +02:00
|
|
|
filters: cosmeticFilterCandidates,
|
|
|
|
slot: max - value,
|
|
|
|
});
|
2020-10-06 23:26:28 +02:00
|
|
|
if ( text === undefined ) { return; }
|
2020-10-14 16:21:30 +02:00
|
|
|
cmEditor.setValue(text);
|
|
|
|
cmEditor.clearHistory();
|
2020-09-10 16:32:53 +02:00
|
|
|
onCandidateChanged();
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const onSpecificityChanged = function() {
|
2020-10-16 23:12:22 +02:00
|
|
|
renderRange('resultsetSpecificity');
|
2020-10-14 16:21:30 +02:00
|
|
|
if ( rawFilterFromTextarea() !== computedCandidate ) { return; }
|
2020-10-20 11:37:07 +02:00
|
|
|
const depthInput = $stor('#resultsetDepth input');
|
|
|
|
const slot = parseInt(depthInput.max, 10) - parseInt(depthInput.value, 10);
|
2020-10-16 23:12:22 +02:00
|
|
|
const i = parseInt($stor('#resultsetSpecificity input').value, 10);
|
2020-10-20 11:37:07 +02:00
|
|
|
const candidates = computedSpecificityCandidates.get(slot);
|
|
|
|
computedCandidate = candidates[i];
|
2020-10-16 23:12:22 +02:00
|
|
|
cmEditor.setValue(computedCandidate);
|
2020-10-14 16:21:30 +02:00
|
|
|
cmEditor.clearHistory();
|
2020-09-10 16:32:53 +02:00
|
|
|
onCandidateChanged();
|
2020-09-09 15:27:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
const onCandidateClicked = function(ev) {
|
|
|
|
let li = ev.target.closest('li');
|
2020-09-03 16:27:35 +02:00
|
|
|
if ( li === null ) { return; }
|
2020-09-01 18:32:12 +02:00
|
|
|
const ul = li.closest('.changeFilter');
|
|
|
|
if ( ul === null ) { return; }
|
|
|
|
const choice = {
|
|
|
|
filters: Array.from(ul.querySelectorAll('li')).map(a => a.textContent),
|
|
|
|
slot: 0,
|
|
|
|
};
|
|
|
|
while ( li.previousElementSibling !== null ) {
|
|
|
|
li = li.previousElementSibling;
|
|
|
|
choice.slot += 1;
|
|
|
|
}
|
2020-10-06 23:26:28 +02:00
|
|
|
const text = candidateFromFilterChoice(choice);
|
|
|
|
if ( text === undefined ) { return; }
|
2020-10-14 16:21:30 +02:00
|
|
|
cmEditor.setValue(text);
|
|
|
|
cmEditor.clearHistory();
|
2020-09-01 18:32:12 +02:00
|
|
|
onCandidateChanged();
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const onKeyPressed = function(ev) {
|
2020-09-11 14:12:45 +02:00
|
|
|
// Delete
|
|
|
|
if (
|
|
|
|
(ev.key === 'Delete' || ev.key === 'Backspace') &&
|
|
|
|
pickerRoot.classList.contains('zap')
|
|
|
|
) {
|
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
|
|
|
what: 'zapElementAtPoint',
|
|
|
|
options: { stay: true },
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2020-09-01 18:32:12 +02:00
|
|
|
// Esc
|
|
|
|
if ( ev.key === 'Escape' || ev.which === 27 ) {
|
|
|
|
onQuitClicked();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const onStartMoving = (( ) => {
|
2020-09-03 16:27:35 +02:00
|
|
|
let isTouch = false;
|
2020-09-01 18:32:12 +02:00
|
|
|
let mx0 = 0, my0 = 0;
|
|
|
|
let mx1 = 0, my1 = 0;
|
|
|
|
let r0 = 0, b0 = 0;
|
|
|
|
let rMax = 0, bMax = 0;
|
|
|
|
let timer;
|
|
|
|
|
2023-01-04 19:43:12 +01:00
|
|
|
const eatEvent = function(ev) {
|
|
|
|
ev.stopPropagation();
|
|
|
|
ev.preventDefault();
|
|
|
|
};
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
const move = ( ) => {
|
|
|
|
timer = undefined;
|
2020-12-05 21:26:29 +01:00
|
|
|
const r1 = Math.min(Math.max(r0 - mx1 + mx0, 2), rMax);
|
|
|
|
const b1 = Math.min(Math.max(b0 - my1 + my0, 2), bMax);
|
2020-09-12 17:36:30 +02:00
|
|
|
dialog.style.setProperty('right', `${r1}px`);
|
|
|
|
dialog.style.setProperty('bottom', `${b1}px`);
|
2020-09-01 18:32:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const moveAsync = ev => {
|
|
|
|
if ( timer !== undefined ) { return; }
|
2020-09-03 16:27:35 +02:00
|
|
|
if ( isTouch ) {
|
|
|
|
const touch = ev.touches[0];
|
|
|
|
mx1 = touch.pageX;
|
|
|
|
my1 = touch.pageY;
|
|
|
|
} else {
|
|
|
|
mx1 = ev.pageX;
|
|
|
|
my1 = ev.pageY;
|
|
|
|
}
|
2020-09-01 18:32:12 +02:00
|
|
|
timer = self.requestAnimationFrame(move);
|
|
|
|
};
|
|
|
|
|
|
|
|
const stop = ev => {
|
|
|
|
if ( dialog.classList.contains('moving') === false ) { return; }
|
|
|
|
dialog.classList.remove('moving');
|
2020-09-03 16:27:35 +02:00
|
|
|
if ( isTouch ) {
|
|
|
|
self.removeEventListener('touchmove', moveAsync, { capture: true });
|
|
|
|
} else {
|
|
|
|
self.removeEventListener('mousemove', moveAsync, { capture: true });
|
|
|
|
}
|
2020-09-01 18:32:12 +02:00
|
|
|
eatEvent(ev);
|
|
|
|
};
|
|
|
|
|
|
|
|
return function(ev) {
|
2020-10-20 11:37:07 +02:00
|
|
|
const target = dialog.querySelector('#move');
|
2020-09-01 18:32:12 +02:00
|
|
|
if ( ev.target !== target ) { return; }
|
|
|
|
if ( dialog.classList.contains('moving') ) { return; }
|
2020-09-03 16:27:35 +02:00
|
|
|
isTouch = ev.type.startsWith('touch');
|
|
|
|
if ( isTouch ) {
|
|
|
|
const touch = ev.touches[0];
|
|
|
|
mx0 = touch.pageX;
|
|
|
|
my0 = touch.pageY;
|
|
|
|
} else {
|
|
|
|
mx0 = ev.pageX;
|
|
|
|
my0 = ev.pageY;
|
|
|
|
}
|
2020-09-01 18:32:12 +02:00
|
|
|
const style = self.getComputedStyle(dialog);
|
|
|
|
r0 = parseInt(style.right, 10);
|
|
|
|
b0 = parseInt(style.bottom, 10);
|
|
|
|
const rect = dialog.getBoundingClientRect();
|
2020-12-05 21:26:29 +01:00
|
|
|
rMax = pickerRoot.clientWidth - 2 - rect.width ;
|
|
|
|
bMax = pickerRoot.clientHeight - 2 - rect.height;
|
2020-09-01 18:32:12 +02:00
|
|
|
dialog.classList.add('moving');
|
2020-09-03 16:27:35 +02:00
|
|
|
if ( isTouch ) {
|
|
|
|
self.addEventListener('touchmove', moveAsync, { capture: true });
|
|
|
|
self.addEventListener('touchend', stop, { capture: true, once: true });
|
|
|
|
} else {
|
|
|
|
self.addEventListener('mousemove', moveAsync, { capture: true });
|
|
|
|
self.addEventListener('mouseup', stop, { capture: true, once: true });
|
|
|
|
}
|
2020-09-01 18:32:12 +02:00
|
|
|
eatEvent(ev);
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-03 16:27:35 +02:00
|
|
|
const svgListening = (( ) => {
|
|
|
|
let on = false;
|
|
|
|
let timer;
|
|
|
|
let mx = 0, my = 0;
|
|
|
|
|
|
|
|
const onTimer = ( ) => {
|
|
|
|
timer = undefined;
|
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
|
|
|
what: 'highlightElementAtPoint',
|
|
|
|
mx,
|
|
|
|
my,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const onHover = ev => {
|
|
|
|
mx = ev.clientX;
|
|
|
|
my = ev.clientY;
|
|
|
|
if ( timer === undefined ) {
|
|
|
|
timer = self.requestAnimationFrame(onTimer);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return state => {
|
|
|
|
if ( state === on ) { return; }
|
|
|
|
on = state;
|
|
|
|
if ( on ) {
|
|
|
|
document.addEventListener('mousemove', onHover, { passive: true });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
document.removeEventListener('mousemove', onHover, { passive: true });
|
|
|
|
if ( timer !== undefined ) {
|
|
|
|
self.cancelAnimationFrame(timer);
|
|
|
|
timer = undefined;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-09 15:27:53 +02:00
|
|
|
// 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 {
|
2020-09-12 17:36:30 +02:00
|
|
|
root.style.setProperty('display', 'none');
|
2020-09-09 15:27:53 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
const showDialog = function(details) {
|
2020-09-03 16:27:35 +02:00
|
|
|
pausePicker();
|
2020-09-01 18:32:12 +02:00
|
|
|
|
2020-09-09 15:27:53 +02:00
|
|
|
const { netFilters, cosmeticFilters, filter } = details;
|
|
|
|
|
|
|
|
netFilterCandidates = netFilters;
|
2020-09-10 16:32:53 +02:00
|
|
|
|
|
|
|
needBody =
|
|
|
|
cosmeticFilters.length !== 0 &&
|
|
|
|
cosmeticFilters[cosmeticFilters.length - 1] === '##body';
|
|
|
|
if ( needBody ) {
|
|
|
|
cosmeticFilters.pop();
|
|
|
|
}
|
2020-09-09 15:27:53 +02:00
|
|
|
cosmeticFilterCandidates = cosmeticFilters;
|
2020-09-01 18:32:12 +02:00
|
|
|
|
2020-10-07 17:52:38 +02:00
|
|
|
docURL.href = details.url;
|
2020-09-01 18:32:12 +02:00
|
|
|
|
2020-09-09 15:27:53 +02:00
|
|
|
populateCandidates(netFilters, '#netFilters');
|
|
|
|
populateCandidates(cosmeticFilters, '#cosmeticFilters');
|
2020-10-20 11:37:07 +02:00
|
|
|
computedSpecificityCandidates.clear();
|
2020-09-01 18:32:12 +02:00
|
|
|
|
2020-09-10 16:32:53 +02:00
|
|
|
const depthInput = $stor('#resultsetDepth input');
|
|
|
|
depthInput.max = cosmeticFilters.length - 1;
|
|
|
|
depthInput.value = depthInput.max;
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
dialog.querySelector('ul').style.display =
|
|
|
|
netFilters.length || cosmeticFilters.length ? '' : 'none';
|
2022-02-09 13:37:40 +01:00
|
|
|
$id('create').setAttribute('disabled', '');
|
2020-09-01 18:32:12 +02:00
|
|
|
|
|
|
|
// Auto-select a candidate filter
|
|
|
|
|
|
|
|
// 2020-09-01:
|
|
|
|
// In Firefox, `details instanceof Object` resolves to `false` despite
|
|
|
|
// `details` being a valid object. Consequently, falling back to use
|
|
|
|
// `typeof details`.
|
|
|
|
// This is an issue which surfaced when the element picker code was
|
|
|
|
// revisited to isolate the picker dialog DOM from the page DOM.
|
|
|
|
if ( typeof filter !== 'object' || filter === null ) {
|
2020-10-14 16:21:30 +02:00
|
|
|
cmEditor.setValue('');
|
2020-09-01 18:32:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const filterChoice = {
|
|
|
|
filters: filter.filters,
|
|
|
|
slot: filter.slot,
|
|
|
|
};
|
|
|
|
|
2020-10-06 23:26:28 +02:00
|
|
|
const text = candidateFromFilterChoice(filterChoice);
|
|
|
|
if ( text === undefined ) { return; }
|
2020-10-14 16:21:30 +02:00
|
|
|
cmEditor.setValue(text);
|
2020-09-01 18:32:12 +02:00
|
|
|
onCandidateChanged();
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-03 16:27:35 +02:00
|
|
|
const pausePicker = function() {
|
|
|
|
pickerRoot.classList.add('paused');
|
|
|
|
svgListening(false);
|
2020-09-01 18:32:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-03 16:27:35 +02:00
|
|
|
const unpausePicker = function() {
|
|
|
|
pickerRoot.classList.remove('paused', 'preview');
|
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
|
|
|
what: 'togglePreview',
|
|
|
|
state: false,
|
|
|
|
});
|
|
|
|
svgListening(true);
|
|
|
|
};
|
2020-09-01 18:32:12 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-03 16:27:35 +02:00
|
|
|
const startPicker = function() {
|
|
|
|
self.addEventListener('keydown', onKeyPressed, true);
|
|
|
|
const svg = $stor('svg');
|
|
|
|
svg.addEventListener('click', onSvgClicked);
|
|
|
|
svg.addEventListener('touchstart', onSvgTouch);
|
|
|
|
svg.addEventListener('touchend', onSvgTouch);
|
|
|
|
|
|
|
|
unpausePicker();
|
|
|
|
|
|
|
|
if ( pickerRoot.classList.contains('zap') ) { return; }
|
|
|
|
|
2020-10-14 16:21:30 +02:00
|
|
|
cmEditor.on('changes', onCandidateChanged);
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
$id('preview').addEventListener('click', onPreviewClicked);
|
|
|
|
$id('create').addEventListener('click', onCreateClicked);
|
|
|
|
$id('pick').addEventListener('click', onPickClicked);
|
|
|
|
$id('quit').addEventListener('click', onQuitClicked);
|
2020-10-20 11:37:07 +02:00
|
|
|
$id('move').addEventListener('mousedown', onStartMoving);
|
|
|
|
$id('move').addEventListener('touchstart', onStartMoving);
|
2020-09-09 15:27:53 +02:00
|
|
|
$id('candidateFilters').addEventListener('click', onCandidateClicked);
|
2020-09-10 16:32:53 +02:00
|
|
|
$stor('#resultsetDepth input').addEventListener('input', onDepthChanged);
|
2020-09-09 15:27:53 +02:00
|
|
|
$stor('#resultsetSpecificity input').addEventListener('input', onSpecificityChanged);
|
2023-01-23 22:53:18 +01:00
|
|
|
staticFilteringParser = new sfp.AstFilterParser({
|
New cosmetic filter parser using CSSTree library
The new parser no longer uses the browser DOM to validate
that a cosmetic filter is valid or not, this is now done
through a JS library, CSSTree.
This means filter list authors will have to be more careful
to ensure that a cosmetic filter is really valid, as there is
no more guarantee that a cosmetic filter which works for a
given browser/version will still work properly on another
browser, or different version of the same browser.
This change has become necessary because of many reasons,
one of them being the flakiness of the previous parser as
exposed by many issues lately:
- https://github.com/uBlockOrigin/uBlock-issues/issues/2262
- https://github.com/uBlockOrigin/uBlock-issues/issues/2228
The new parser introduces breaking changes, there was no way
to do otherwise. Some current procedural cosmetic filters will
be shown as invalid with this change. This occurs because the
CSSTree library gets confused with some syntax which was
previously allowed by the previous parser because it was more
permissive.
Mainly the issue is with the arguments passed to some procedural
cosmetic filters, and these issues can be solved as follow:
Use quotes around the argument. You can use either single or
double-quotes, whichever is most convenient. If your argument
contains a single quote, use double-quotes, and vice versa.
Additionally, try to escape a quote inside an argument using
backslash. THis may work, but if not, use quotes around the
argument.
When the parser encounter quotes around an argument, it will
discard them before trying to process the argument, same with
escaped quotes inside the argument. Examples:
Breakage:
...##^script:has-text(toscr')
Fix:
...##^script:has-text(toscr\')
Breakage:
...##:xpath(//*[contains(text(),"VPN")]):upward(2)
Fix:
...##:xpath('//*[contains(text(),"VPN")]'):upward(2)
There are not many filters which break in the default set of
filter lists, so this should be workable for default lists.
Unfortunately those fixes will break the filter for previous
versions of uBO since these to not deal with quoted argument.
In such case, it may be necessary to keep the previous filter,
which will be discarded as broken on newer version of uBO.
THis was a necessary change as the old parser was becoming
more and more flaky after being constantly patched for new
cases arising, The new parser should be far more robust and
stay robist through expanding procedural cosmetic filter
syntax.
Additionally, in the MV3 version, filters are pre-compiled
using a Nodejs script, i.e. outside the browser, so validating
cosmetic filters using a live DOM no longer made sense.
This new parser will have to be tested throughly before stable
release.
2022-09-23 22:03:13 +02:00
|
|
|
interactive: true,
|
|
|
|
nativeCssHas: vAPI.webextFlavor.env.includes('native_css_has'),
|
|
|
|
});
|
2020-09-01 18:32:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-03 16:27:35 +02:00
|
|
|
const quitPicker = function() {
|
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, { what: 'quitPicker' });
|
|
|
|
vAPI.MessagingConnection.disconnectFrom(epickerConnectionId);
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-09-01 18:32:12 +02:00
|
|
|
const onPickerMessage = function(msg) {
|
|
|
|
switch ( msg.what ) {
|
2020-10-16 23:12:22 +02:00
|
|
|
case 'candidatesOptimized':
|
|
|
|
onCandidatesOptimized(msg);
|
2020-10-06 23:26:28 +02:00
|
|
|
break;
|
2020-09-03 16:27:35 +02:00
|
|
|
case 'showDialog':
|
|
|
|
showDialog(msg);
|
|
|
|
break;
|
2020-09-07 14:28:01 +02:00
|
|
|
case 'resultsetDetails': {
|
|
|
|
resultsetOpt = msg.opt;
|
|
|
|
$id('resultsetCount').textContent = msg.count;
|
|
|
|
if ( msg.count !== 0 ) {
|
2020-09-03 16:27:35 +02:00
|
|
|
$id('create').removeAttribute('disabled');
|
|
|
|
} else {
|
|
|
|
$id('create').setAttribute('disabled', '');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'svgPaths': {
|
|
|
|
let { ocean, islands } = msg;
|
|
|
|
ocean += islands;
|
|
|
|
svgOcean.setAttribute('d', ocean);
|
|
|
|
svgIslands.setAttribute('d', islands || NoPaths);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
2020-09-01 18:32:12 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const onConnectionMessage = function(msg) {
|
|
|
|
switch ( msg.what ) {
|
2020-09-03 16:27:35 +02:00
|
|
|
case 'connectionBroken':
|
|
|
|
break;
|
|
|
|
case 'connectionMessage':
|
|
|
|
onPickerMessage(msg.payload);
|
|
|
|
break;
|
|
|
|
case 'connectionAccepted':
|
|
|
|
epickerConnectionId = msg.id;
|
|
|
|
startPicker();
|
|
|
|
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
|
|
|
|
what: 'start',
|
|
|
|
});
|
|
|
|
break;
|
2020-09-01 18:32:12 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
vAPI.MessagingConnection.connectTo(
|
|
|
|
`epickerDialog-${epickerId}`,
|
|
|
|
`epicker-${epickerId}`,
|
|
|
|
onConnectionMessage
|
|
|
|
);
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
DO NOT:
|
|
|
|
- Remove the following code
|
|
|
|
- Add code beyond the following code
|
|
|
|
Reason:
|
|
|
|
- https://github.com/gorhill/uBlock/pull/3721
|
|
|
|
- uBO never uses the return value from injected content scripts
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
void 0;
|