1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-09-29 14:17:11 +02:00
uBlock/src/js/epicker-ui.js
Raymond Hill 22022f636f
Modularize codebase with export/import
Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1664

The changes are enough to fulfill the related issue.

A new platform has been added in order to allow for building
a NodeJS package. From the root of the project:

    ./tools/make-nodejs

This will create new uBlock0.nodejs directory in the
./dist/build directory, which is a valid NodeJS package.

From the root of the package, you can try:

    node test

This will instantiate a static network filtering engine,
populated by easylist and easyprivacy, which can be used
to match network requests by filling the appropriate
filtering context object.

The test.js file contains code which is typical example
of usage of the package.

Limitations: the NodeJS package can't execute the WASM
versions of the code since the WASM module requires the
use of fetch(), which is not available in NodeJS.

This is a first pass at modularizing the codebase, and
while at it a number of opportunistic small rewrites
have also been made.

This commit requires the minimum supported version for
Chromium and Firefox be raised to 61 and 60 respectively.
2021-07-27 17:26:04 -04:00

928 lines
29 KiB
JavaScript

/*******************************************************************************
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
*/
/* global CodeMirror */
'use strict';
/******************************************************************************/
import './codemirror/ubo-static-filtering.js';
import { hostnameFromURI } from './uri-utils.js';
import { StaticFilteringParser } from './static-filtering-parser.js';
/******************************************************************************/
/******************************************************************************/
(( ) => {
/******************************************************************************/
if ( typeof vAPI !== 'object' ) { return; }
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';
const reCosmeticAnchor = /^#[$?]?#/;
const epickerId = (( ) => {
const url = new URL(self.location.href);
if ( url.searchParams.has('zap') ) {
pickerRoot.classList.add('zap');
}
return url.searchParams.get('epid');
})();
if ( epickerId === null ) { return; }
const docURL = new URL(vAPI.getURL(''));
let epickerConnectionId;
let resultsetOpt;
let netFilterCandidates = [];
let cosmeticFilterCandidates = [];
let computedCandidateSlot = 0;
let computedCandidate = '';
const computedSpecificityCandidates = new Map();
let needBody = false;
/******************************************************************************/
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 => {
// For unknown reasons, `instanceof Object` does not work here in Firefox.
if ( typeof response !== 'object' ) { return; }
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);
};
/******************************************************************************/
const filterFromTextarea = function() {
const filter = rawFilterFromTextarea();
if ( filter === '' ) { return ''; }
const sfp = staticFilteringParser;
sfp.analyze(filter);
sfp.analyzeExtra();
if (
sfp.category !== sfp.CATStaticExtFilter &&
sfp.category !== sfp.CATStaticNetFilter ||
sfp.shouldDiscard()
) {
return '!';
}
return filter;
};
/******************************************************************************/
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;
const slider = $stor(`#${id} > span`);
const lside = slider.children[0];
const thumb = slider.children[1];
const sliderWidth = slider.offsetWidth;
const maxPercent = (sliderWidth - thumb.offsetWidth) / sliderWidth * 100;
const widthPercent = value / max * maxPercent;
lside.style.width = `${widthPercent}%`;
};
/******************************************************************************/
const userFilterFromCandidate = function(filter) {
if ( filter === '' || filter === '!' ) { return; }
const hn = hostnameFromURI(docURL.href);
// Cosmetic filter?
if ( reCosmeticAnchor.test(filter) ) {
return hn + filter;
}
// Assume net filter
const opts = [];
// If no domain included in filter, we need domain option
if ( filter.startsWith('||') === false ) {
opts.push(`domain=${hn}`);
}
if ( resultsetOpt !== undefined ) {
opts.push(resultsetOpt);
}
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');
}
computedCandidateSlot = slot;
computedCandidate = '';
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');
return cosmeticCandidatesFromFilterChoice(filterChoice);
};
/******************************************************************************/
const cosmeticCandidatesFromFilterChoice = function(filterChoice) {
let { slot, filters } = filterChoice;
renderRange('resultsetDepth', slot, true);
renderRange('resultsetSpecificity');
if ( computedSpecificityCandidates.has(slot) ) {
onCandidatesOptimized({ slot });
return;
}
const specificities = [
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
];
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);
}
}
}
// 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(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;
}
}
// 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;
}
}
}
if (
needBody &&
paths.length !== 0 &&
paths[0].startsWith('#') === false &&
paths[0].startsWith('body ') === false &&
(specificity & 0b1100) !== 0
) {
paths.unshift('body > ');
}
candidates.push(paths);
}
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
what: 'optimizeCandidates',
candidates,
slot,
});
};
/******************************************************************************/
const onCandidatesOptimized = function(details) {
$id('resultsetModifiers').classList.remove('hide');
const i = parseInt($stor('#resultsetSpecificity input').value, 10);
if ( Array.isArray(details.candidates) ) {
computedSpecificityCandidates.set(details.slot, details.candidates);
}
const candidates = computedSpecificityCandidates.get(details.slot);
computedCandidate = candidates[i];
cmEditor.setValue(computedCandidate);
cmEditor.clearHistory();
onCandidateChanged();
};
/******************************************************************************/
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;
}
if ( ev.cancelable ) {
ev.preventDefault();
}
// 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();
};
})();
/******************************************************************************/
const onCandidateChanged = function() {
const filter = filterFromTextarea();
const bad = filter === '!';
$stor('section').classList.toggle('invalidFilter', bad);
$id('create').disabled = bad;
if ( bad ) {
$id('resultsetCount').textContent = 'E';
$id('create').setAttribute('disabled', '');
}
const text = rawFilterFromTextarea();
$id('resultsetModifiers').classList.toggle(
'hide', text === '' || text !== computedCandidate
);
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
what: 'dialogSetFilter',
filter,
compiled: reCosmeticAnchor.test(filter)
? staticFilteringParser.result.compiled
: undefined,
});
};
/******************************************************************************/
const onPreviewClicked = function() {
const state = pickerRoot.classList.toggle('preview');
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
what: 'togglePreview',
state,
});
};
/******************************************************************************/
const onCreateClicked = function() {
const candidate = filterFromTextarea();
const filter = userFilterFromCandidate(candidate);
if ( filter !== undefined ) {
vAPI.messaging.send('elementPicker', {
what: 'createUserFilter',
autoComment: true,
filters: filter,
docURL: docURL.href,
killCache: reCosmeticAnchor.test(candidate) === false,
});
}
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
what: 'dialogCreate',
filter: candidate,
compiled: reCosmeticAnchor.test(candidate)
? staticFilteringParser.result.compiled
: undefined,
});
};
/******************************************************************************/
const onPickClicked = function() {
unpausePicker();
};
/******************************************************************************/
const onQuitClicked = function() {
quitPicker();
};
/******************************************************************************/
const onDepthChanged = function() {
const input = $stor('#resultsetDepth input');
const max = parseInt(input.max, 10);
const value = parseInt(input.value, 10);
const text = candidateFromFilterChoice({
filters: cosmeticFilterCandidates,
slot: max - value,
});
if ( text === undefined ) { return; }
cmEditor.setValue(text);
cmEditor.clearHistory();
onCandidateChanged();
};
/******************************************************************************/
const onSpecificityChanged = function() {
renderRange('resultsetSpecificity');
if ( rawFilterFromTextarea() !== computedCandidate ) { return; }
const depthInput = $stor('#resultsetDepth input');
const slot = parseInt(depthInput.max, 10) - parseInt(depthInput.value, 10);
const i = parseInt($stor('#resultsetSpecificity input').value, 10);
const candidates = computedSpecificityCandidates.get(slot);
computedCandidate = candidates[i];
cmEditor.setValue(computedCandidate);
cmEditor.clearHistory();
onCandidateChanged();
};
/******************************************************************************/
const onCandidateClicked = function(ev) {
let li = ev.target.closest('li');
if ( li === null ) { return; }
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;
}
const text = candidateFromFilterChoice(choice);
if ( text === undefined ) { return; }
cmEditor.setValue(text);
cmEditor.clearHistory();
onCandidateChanged();
};
/******************************************************************************/
const onKeyPressed = function(ev) {
// Delete
if (
(ev.key === 'Delete' || ev.key === 'Backspace') &&
pickerRoot.classList.contains('zap')
) {
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
what: 'zapElementAtPoint',
options: { stay: true },
});
return;
}
// Esc
if ( ev.key === 'Escape' || ev.which === 27 ) {
onQuitClicked();
return;
}
};
/******************************************************************************/
const onStartMoving = (( ) => {
let isTouch = false;
let mx0 = 0, my0 = 0;
let mx1 = 0, my1 = 0;
let r0 = 0, b0 = 0;
let rMax = 0, bMax = 0;
let timer;
const move = ( ) => {
timer = undefined;
const r1 = Math.min(Math.max(r0 - mx1 + mx0, 2), rMax);
const b1 = Math.min(Math.max(b0 - my1 + my0, 2), bMax);
dialog.style.setProperty('right', `${r1}px`);
dialog.style.setProperty('bottom', `${b1}px`);
};
const moveAsync = ev => {
if ( timer !== undefined ) { return; }
if ( isTouch ) {
const touch = ev.touches[0];
mx1 = touch.pageX;
my1 = touch.pageY;
} else {
mx1 = ev.pageX;
my1 = ev.pageY;
}
timer = self.requestAnimationFrame(move);
};
const stop = ev => {
if ( dialog.classList.contains('moving') === false ) { return; }
dialog.classList.remove('moving');
if ( isTouch ) {
self.removeEventListener('touchmove', moveAsync, { capture: true });
} else {
self.removeEventListener('mousemove', moveAsync, { capture: true });
}
eatEvent(ev);
};
return function(ev) {
const target = dialog.querySelector('#move');
if ( ev.target !== target ) { return; }
if ( dialog.classList.contains('moving') ) { return; }
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;
}
const style = self.getComputedStyle(dialog);
r0 = parseInt(style.right, 10);
b0 = parseInt(style.bottom, 10);
const rect = dialog.getBoundingClientRect();
rMax = pickerRoot.clientWidth - 2 - rect.width ;
bMax = pickerRoot.clientHeight - 2 - rect.height;
dialog.classList.add('moving');
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 });
}
eatEvent(ev);
};
})();
/******************************************************************************/
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;
}
};
})();
/******************************************************************************/
const eatEvent = function(ev) {
ev.stopPropagation();
ev.preventDefault();
};
/******************************************************************************/
// 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');
}
};
/******************************************************************************/
const showDialog = function(details) {
pausePicker();
const { netFilters, cosmeticFilters, filter } = details;
netFilterCandidates = netFilters;
needBody =
cosmeticFilters.length !== 0 &&
cosmeticFilters[cosmeticFilters.length - 1] === '##body';
if ( needBody ) {
cosmeticFilters.pop();
}
cosmeticFilterCandidates = cosmeticFilters;
docURL.href = details.url;
populateCandidates(netFilters, '#netFilters');
populateCandidates(cosmeticFilters, '#cosmeticFilters');
computedSpecificityCandidates.clear();
const depthInput = $stor('#resultsetDepth input');
depthInput.max = cosmeticFilters.length - 1;
depthInput.value = depthInput.max;
dialog.querySelector('ul').style.display =
netFilters.length || cosmeticFilters.length ? '' : 'none';
dialog.querySelector('#create').disabled = true;
// 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 ) {
cmEditor.setValue('');
return;
}
const filterChoice = {
filters: filter.filters,
slot: filter.slot,
};
const text = candidateFromFilterChoice(filterChoice);
if ( text === undefined ) { return; }
cmEditor.setValue(text);
onCandidateChanged();
};
/******************************************************************************/
const pausePicker = function() {
pickerRoot.classList.add('paused');
svgListening(false);
};
/******************************************************************************/
const unpausePicker = function() {
pickerRoot.classList.remove('paused', 'preview');
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
what: 'togglePreview',
state: false,
});
svgListening(true);
};
/******************************************************************************/
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; }
cmEditor.on('changes', onCandidateChanged);
$id('preview').addEventListener('click', onPreviewClicked);
$id('create').addEventListener('click', onCreateClicked);
$id('pick').addEventListener('click', onPickClicked);
$id('quit').addEventListener('click', onQuitClicked);
$id('move').addEventListener('mousedown', onStartMoving);
$id('move').addEventListener('touchstart', onStartMoving);
$id('candidateFilters').addEventListener('click', onCandidateClicked);
$stor('#resultsetDepth input').addEventListener('input', onDepthChanged);
$stor('#resultsetSpecificity input').addEventListener('input', onSpecificityChanged);
staticFilteringParser = new StaticFilteringParser({ interactive: true });
};
/******************************************************************************/
const quitPicker = function() {
vAPI.MessagingConnection.sendTo(epickerConnectionId, { what: 'quitPicker' });
vAPI.MessagingConnection.disconnectFrom(epickerConnectionId);
};
/******************************************************************************/
const onPickerMessage = function(msg) {
switch ( msg.what ) {
case 'candidatesOptimized':
onCandidatesOptimized(msg);
break;
case 'showDialog':
showDialog(msg);
break;
case 'resultsetDetails': {
resultsetOpt = msg.opt;
$id('resultsetCount').textContent = msg.count;
if ( msg.count !== 0 ) {
$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;
}
};
/******************************************************************************/
const onConnectionMessage = function(msg) {
switch ( msg.what ) {
case 'connectionBroken':
break;
case 'connectionMessage':
onPickerMessage(msg.payload);
break;
case 'connectionAccepted':
epickerConnectionId = msg.id;
startPicker();
vAPI.MessagingConnection.sendTo(epickerConnectionId, {
what: 'start',
});
break;
}
};
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;