2014-07-13 02:32:44 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
2016-03-06 16:51:06 +01:00
|
|
|
|
uBlock Origin - a browser extension to block requests.
|
2018-08-15 13:58:42 +02:00
|
|
|
|
Copyright (C) 2014-present Raymond Hill
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
*/
|
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
/* global CSS */
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
'use strict';
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */
|
|
|
|
|
;(function(root) {
|
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
if (!root.CSS) {
|
|
|
|
|
root.CSS = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var CSS = root.CSS;
|
|
|
|
|
|
|
|
|
|
var InvalidCharacterError = function(message) {
|
|
|
|
|
this.message = message;
|
|
|
|
|
};
|
|
|
|
|
InvalidCharacterError.prototype = new Error();
|
|
|
|
|
InvalidCharacterError.prototype.name = 'InvalidCharacterError';
|
|
|
|
|
|
|
|
|
|
if (!CSS.escape) {
|
|
|
|
|
// http://dev.w3.org/csswg/cssom/#serialize-an-identifier
|
|
|
|
|
CSS.escape = function(value) {
|
|
|
|
|
var string = String(value);
|
|
|
|
|
var length = string.length;
|
|
|
|
|
var index = -1;
|
|
|
|
|
var codeUnit;
|
|
|
|
|
var result = '';
|
|
|
|
|
var firstCodeUnit = string.charCodeAt(0);
|
|
|
|
|
while (++index < length) {
|
|
|
|
|
codeUnit = string.charCodeAt(index);
|
|
|
|
|
// Note: there’s no need to special-case astral symbols, surrogate
|
|
|
|
|
// pairs, or lone surrogates.
|
|
|
|
|
|
|
|
|
|
// If the character is NULL (U+0000), then throw an
|
|
|
|
|
// `InvalidCharacterError` exception and terminate these steps.
|
|
|
|
|
if (codeUnit === 0x0000) {
|
|
|
|
|
throw new InvalidCharacterError(
|
|
|
|
|
'Invalid character: the input contains U+0000.'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
|
|
|
|
|
// U+007F, […]
|
2016-01-17 19:30:43 +01:00
|
|
|
|
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit === 0x007F ||
|
2014-10-23 14:12:37 +02:00
|
|
|
|
// If the character is the first character and is in the range [0-9]
|
|
|
|
|
// (U+0030 to U+0039), […]
|
|
|
|
|
(index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
|
|
|
|
|
// If the character is the second character and is in the range [0-9]
|
|
|
|
|
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
|
|
|
|
|
(
|
2016-01-17 19:30:43 +01:00
|
|
|
|
index === 1 &&
|
2014-10-23 14:12:37 +02:00
|
|
|
|
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
|
2016-01-17 19:30:43 +01:00
|
|
|
|
firstCodeUnit === 0x002D
|
2014-10-23 14:12:37 +02:00
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
// http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point
|
|
|
|
|
result += '\\' + codeUnit.toString(16) + ' ';
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the character is not handled by one of the above rules and is
|
|
|
|
|
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
|
|
|
|
|
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
|
|
|
|
|
// U+005A), or [a-z] (U+0061 to U+007A), […]
|
|
|
|
|
if (
|
|
|
|
|
codeUnit >= 0x0080 ||
|
2016-01-17 19:30:43 +01:00
|
|
|
|
codeUnit === 0x002D ||
|
|
|
|
|
codeUnit === 0x005F ||
|
2014-10-23 14:12:37 +02:00
|
|
|
|
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
|
|
|
|
|
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
|
|
|
|
|
codeUnit >= 0x0061 && codeUnit <= 0x007A
|
|
|
|
|
) {
|
|
|
|
|
// the character itself
|
|
|
|
|
result += string.charAt(index);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise, the escaped character.
|
|
|
|
|
// http://dev.w3.org/csswg/cssom/#escape-a-character
|
|
|
|
|
result += '\\' + string.charAt(index);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2014-11-02 17:20:06 +01:00
|
|
|
|
}(self));
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
(( ) => {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
|
if (
|
|
|
|
|
window.top !== window ||
|
|
|
|
|
typeof vAPI !== 'object' ||
|
|
|
|
|
vAPI.domFilterer instanceof Object === false
|
|
|
|
|
) {
|
2014-11-04 12:32:44 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-05 18:03:48 +01:00
|
|
|
|
let pickerRoot = document.getElementById(vAPI.sessionId);
|
2019-06-29 17:06:03 +02:00
|
|
|
|
if ( pickerRoot ) { return; }
|
2017-10-21 19:43:46 +02:00
|
|
|
|
|
2019-11-05 18:03:48 +01:00
|
|
|
|
let pickerBody = null;
|
|
|
|
|
let svgOcean = null;
|
|
|
|
|
let svgIslands = null;
|
|
|
|
|
let svgRoot = null;
|
|
|
|
|
let dialog = null;
|
|
|
|
|
let taCandidate = null;
|
2014-09-28 18:05:46 +02:00
|
|
|
|
|
2019-11-05 18:03:48 +01:00
|
|
|
|
const netFilterCandidates = [];
|
|
|
|
|
const cosmeticFilterCandidates = [];
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2019-11-05 18:03:48 +01:00
|
|
|
|
let targetElements = [];
|
|
|
|
|
let candidateElements = [];
|
|
|
|
|
let bestCandidateFilter = null;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2019-11-05 18:03:48 +01:00
|
|
|
|
const lastNetFilterSession = window.location.host + window.location.pathname;
|
|
|
|
|
let lastNetFilterHostname = '';
|
|
|
|
|
let lastNetFilterUnion = '';
|
2015-03-20 16:39:20 +01:00
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2015-01-30 06:49:30 +01:00
|
|
|
|
// For browsers not supporting `:scope`, it's not the end of the world: the
|
|
|
|
|
// suggested CSS selectors may just end up being more verbose.
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
let cssScope = ':scope > ';
|
2015-01-30 06:49:30 +01:00
|
|
|
|
try {
|
|
|
|
|
document.querySelector(':scope *');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
cssScope = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const safeQuerySelectorAll = function(node, selector) {
|
2015-02-16 17:21:25 +01:00
|
|
|
|
if ( node !== null ) {
|
|
|
|
|
try {
|
|
|
|
|
return node.querySelectorAll(selector);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
}
|
2015-02-16 17:14:37 +01:00
|
|
|
|
}
|
2015-02-16 17:21:25 +01:00
|
|
|
|
return [];
|
2015-02-16 17:14:37 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const rawFilterFromTextarea = function() {
|
|
|
|
|
const s = taCandidate.value;
|
|
|
|
|
const pos = s.indexOf('\n');
|
2016-12-25 22:56:39 +01:00
|
|
|
|
return pos === -1 ? s.trim() : s.slice(0, pos).trim();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const getElementBoundingClientRect = function(elem) {
|
|
|
|
|
let rect = typeof elem.getBoundingClientRect === 'function'
|
|
|
|
|
? elem.getBoundingClientRect()
|
|
|
|
|
: { height: 0, left: 0, top: 0, width: 0 };
|
2015-12-07 17:09:39 +01:00
|
|
|
|
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1024
|
|
|
|
|
// Try not returning an empty bounding rect.
|
2015-12-07 17:18:56 +01:00
|
|
|
|
if ( rect.width !== 0 && rect.height !== 0 ) {
|
|
|
|
|
return rect;
|
2015-12-07 17:09:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
let left = rect.left,
|
2015-12-07 17:18:56 +01:00
|
|
|
|
right = rect.right,
|
|
|
|
|
top = rect.top,
|
|
|
|
|
bottom = rect.bottom;
|
2015-12-07 17:09:39 +01:00
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
for ( const child of elem.children ) {
|
|
|
|
|
rect = getElementBoundingClientRect(child);
|
2015-12-07 17:18:56 +01:00
|
|
|
|
if ( rect.width === 0 || rect.height === 0 ) {
|
2015-12-07 17:09:39 +01:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-12-07 17:18:56 +01:00
|
|
|
|
if ( rect.left < left ) { left = rect.left; }
|
|
|
|
|
if ( rect.right > right ) { right = rect.right; }
|
|
|
|
|
if ( rect.top < top ) { top = rect.top; }
|
|
|
|
|
if ( rect.bottom > bottom ) { bottom = rect.bottom; }
|
2015-12-07 17:09:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
height: bottom - top,
|
|
|
|
|
left: left,
|
|
|
|
|
top: top,
|
|
|
|
|
width: right - left
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const highlightElements = function(elems, force) {
|
2014-07-14 08:14:13 +02:00
|
|
|
|
// To make mouse move handler more efficient
|
2014-07-13 02:32:44 +02:00
|
|
|
|
if ( !force && elems.length === targetElements.length ) {
|
|
|
|
|
if ( elems.length === 0 || elems[0] === targetElements[0] ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
targetElements = elems;
|
2014-10-09 16:41:20 +02:00
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const ow = pickerRoot.contentWindow.innerWidth;
|
|
|
|
|
const oh = pickerRoot.contentWindow.innerHeight;
|
|
|
|
|
const ocean = [
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'M0 0',
|
|
|
|
|
'h', ow,
|
2015-02-09 14:03:29 +01:00
|
|
|
|
'v', oh,
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'h-', ow,
|
|
|
|
|
'z'
|
|
|
|
|
];
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const islands = [];
|
2014-11-07 12:59:01 +01:00
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
for ( let i = 0; i < elems.length; i++ ) {
|
|
|
|
|
const elem = elems[i];
|
2015-02-10 18:59:27 +01:00
|
|
|
|
if ( elem === pickerRoot ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const rect = getElementBoundingClientRect(elem);
|
2015-02-09 14:03:29 +01:00
|
|
|
|
|
|
|
|
|
// Ignore if it's not on the screen
|
|
|
|
|
if ( rect.left > ow || rect.top > oh ||
|
|
|
|
|
rect.left + rect.width < 0 || rect.top + rect.height < 0 ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const poly = 'M' + rect.left + ' ' + rect.top +
|
2014-11-03 15:28:55 +01:00
|
|
|
|
'h' + rect.width +
|
|
|
|
|
'v' + rect.height +
|
|
|
|
|
'h-' + rect.width +
|
|
|
|
|
'z';
|
2014-10-11 22:43:17 +02:00
|
|
|
|
ocean.push(poly);
|
|
|
|
|
islands.push(poly);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
|
|
|
|
svgOcean.setAttribute('d', ocean.join(''));
|
2014-10-23 14:12:37 +02:00
|
|
|
|
svgIslands.setAttribute('d', islands.join('') || 'M0 0');
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const mergeStrings = function(urls) {
|
|
|
|
|
if ( urls.length === 0 ) { return ''; }
|
|
|
|
|
if (
|
|
|
|
|
urls.length === 1 ||
|
|
|
|
|
self.diff_match_patch instanceof Function === false
|
|
|
|
|
) {
|
|
|
|
|
return urls[0];
|
|
|
|
|
}
|
|
|
|
|
const differ = new self.diff_match_patch();
|
|
|
|
|
let merged = urls[0];
|
|
|
|
|
for ( let i = 1; i < urls.length; i++ ) {
|
|
|
|
|
// The differ works at line granularity: we insert a linefeed after
|
|
|
|
|
// each character to trick the differ to work at character granularity.
|
|
|
|
|
const diffs = differ.diff_main(
|
|
|
|
|
//urls[i].replace(/.(?=.)/g, '$&\n'),
|
|
|
|
|
//merged.replace(/.(?=.)/g, '$&\n')
|
|
|
|
|
urls[i].split('').join('\n'),
|
|
|
|
|
merged.split('').join('\n')
|
|
|
|
|
);
|
|
|
|
|
const result = [];
|
|
|
|
|
for ( const diff of diffs ) {
|
|
|
|
|
if ( diff[0] !== 0 ) {
|
|
|
|
|
result.push('*');
|
|
|
|
|
} else {
|
2019-06-25 23:09:04 +02:00
|
|
|
|
result.push(diff[1].replace(/\n+/g, ''));
|
2018-12-20 23:29:39 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Keep usage of wildcards to a sane level, too many of them can cause
|
|
|
|
|
// high overhead filters
|
|
|
|
|
merged =
|
|
|
|
|
result.join('')
|
|
|
|
|
.replace(/\*+$/, '')
|
|
|
|
|
.replace(/\*{2,}/g, '*')
|
|
|
|
|
.replace(/([^*]{1,2}\*)(?:[^*]{1,2}\*)+/g, '$1');
|
|
|
|
|
}
|
|
|
|
|
return merged;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2016-08-15 13:52:50 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1897
|
|
|
|
|
// Ignore `data:` URI, they can't be handled by an HTTP observer.
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const backgroundImageURLFromElement = function(elem) {
|
|
|
|
|
const style = window.getComputedStyle(elem);
|
|
|
|
|
const bgImg = style.backgroundImage || '';
|
|
|
|
|
const matches = /^url\((["']?)([^"']+)\1\)$/.exec(bgImg);
|
|
|
|
|
const url = matches !== null && matches.length === 3 ? matches[2] : '';
|
2016-08-15 13:52:50 +02:00
|
|
|
|
return url.lastIndexOf('data:', 0) === -1 ? url.slice(0, 1024) : '';
|
2016-04-15 18:27:53 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2016-06-16 15:15:49 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1725#issuecomment-226479197
|
2018-12-20 23:29:39 +01:00
|
|
|
|
// Limit returned string to 1024 characters.
|
|
|
|
|
// Also, return only URLs which will be seen by an HTTP observer.
|
2016-06-16 15:15:49 +02:00
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const resourceURLFromElement = function(elem) {
|
|
|
|
|
const tagName = elem.localName;
|
|
|
|
|
const prop = netFilter1stSources[tagName];
|
|
|
|
|
if ( prop ) {
|
|
|
|
|
let src = '';
|
|
|
|
|
{
|
|
|
|
|
let s = elem[prop];
|
|
|
|
|
if ( typeof s === 'string' && /^https?:\/\//.test(s) ) {
|
|
|
|
|
src = s.slice(0, 1024);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( typeof elem.srcset === 'string' && elem.srcset !== '' ) {
|
|
|
|
|
const ss = [];
|
2019-04-14 23:37:48 +02:00
|
|
|
|
for ( let s of elem.srcset.split(',') ) {
|
2019-06-25 23:09:04 +02:00
|
|
|
|
s = s.trim();
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const pos = s.indexOf(' ');
|
|
|
|
|
if ( pos !== -1 ) { s = s.slice(0, pos); }
|
|
|
|
|
const parsedURL = new URL(s, document.baseURI);
|
|
|
|
|
if ( parsedURL.pathname.length > 1 ) {
|
|
|
|
|
ss.push(parsedURL.href);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( ss.length !== 0 ) {
|
|
|
|
|
if ( src !== '' ) {
|
|
|
|
|
ss.push(src);
|
|
|
|
|
}
|
|
|
|
|
src = mergeStrings(ss);
|
|
|
|
|
}
|
2016-04-15 18:27:53 +02:00
|
|
|
|
}
|
2018-12-20 23:29:39 +01:00
|
|
|
|
return src;
|
2016-04-15 18:27:53 +02:00
|
|
|
|
}
|
|
|
|
|
return backgroundImageURLFromElement(elem);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const netFilterFromUnion = function(toMergeURL, out) {
|
|
|
|
|
const parsedURL = new URL(toMergeURL, document.baseURI);
|
2015-03-19 15:13:51 +01:00
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
toMergeURL = parsedURL.pathname + parsedURL.search;
|
2015-03-19 15:13:51 +01:00
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
// Reset reference filter when dealing with unrelated URLs
|
|
|
|
|
if (
|
|
|
|
|
lastNetFilterUnion === '' ||
|
|
|
|
|
parsedURL.host === '' ||
|
|
|
|
|
parsedURL.host !== lastNetFilterHostname
|
|
|
|
|
) {
|
|
|
|
|
lastNetFilterHostname = parsedURL.host;
|
|
|
|
|
lastNetFilterUnion = toMergeURL;
|
2019-09-17 21:15:01 +02:00
|
|
|
|
vAPI.messaging.send('elementPicker', {
|
|
|
|
|
what: 'elementPickerEprom',
|
|
|
|
|
lastNetFilterSession: lastNetFilterSession,
|
|
|
|
|
lastNetFilterHostname: lastNetFilterHostname,
|
|
|
|
|
lastNetFilterUnion: lastNetFilterUnion,
|
|
|
|
|
});
|
2018-12-20 23:29:39 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Related URLs
|
|
|
|
|
lastNetFilterHostname = parsedURL.host;
|
|
|
|
|
|
|
|
|
|
let mergedURL = mergeStrings([ toMergeURL, lastNetFilterUnion ]);
|
|
|
|
|
if ( mergedURL !== '/*' && mergedURL !== toMergeURL ) {
|
|
|
|
|
const filter = '||' + lastNetFilterHostname + mergedURL;
|
|
|
|
|
if ( out.indexOf(filter) === -1 ) {
|
|
|
|
|
out.push(filter);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
mergedURL = toMergeURL;
|
|
|
|
|
}
|
|
|
|
|
lastNetFilterUnion = mergedURL;
|
|
|
|
|
|
|
|
|
|
// Remember across element picker sessions
|
2019-09-17 21:15:01 +02:00
|
|
|
|
vAPI.messaging.send('elementPicker', {
|
|
|
|
|
what: 'elementPickerEprom',
|
|
|
|
|
lastNetFilterSession: lastNetFilterSession,
|
|
|
|
|
lastNetFilterHostname: lastNetFilterHostname,
|
|
|
|
|
lastNetFilterUnion: lastNetFilterUnion,
|
|
|
|
|
});
|
2018-12-20 23:29:39 +01:00
|
|
|
|
};
|
2015-03-19 15:13:51 +01:00
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
// Extract the best possible net filter, i.e. as specific as possible.
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const netFilterFromElement = function(elem) {
|
2018-11-06 19:11:03 +01:00
|
|
|
|
if ( elem === null ) { return 0; }
|
|
|
|
|
if ( elem.nodeType !== 1 ) { return 0; }
|
|
|
|
|
let src = resourceURLFromElement(elem);
|
|
|
|
|
if ( src === '' ) { return 0; }
|
2016-04-15 18:27:53 +02:00
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
if ( candidateElements.indexOf(elem) === -1 ) {
|
|
|
|
|
candidateElements.push(elem);
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const candidates = netFilterCandidates;
|
|
|
|
|
const len = candidates.length;
|
2016-04-16 17:20:01 +02:00
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
// Remove fragment
|
2018-11-06 19:11:03 +01:00
|
|
|
|
let pos = src.indexOf('#');
|
2014-09-28 18:05:46 +02:00
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
|
src = src.slice(0, pos);
|
|
|
|
|
}
|
2015-03-19 15:13:51 +01:00
|
|
|
|
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const filter = src.replace(/^https?:\/\//, '||');
|
2015-03-19 15:13:51 +01:00
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
if ( bestCandidateFilter === null ) {
|
|
|
|
|
bestCandidateFilter = {
|
2016-04-17 16:15:01 +02:00
|
|
|
|
type: 'net',
|
2016-04-16 17:20:01 +02:00
|
|
|
|
filters: candidates,
|
|
|
|
|
slot: candidates.length
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
candidates.push(filter);
|
2015-03-19 15:13:51 +01:00
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
// Suggest a less narrow filter if possible
|
2015-03-19 15:13:51 +01:00
|
|
|
|
pos = filter.indexOf('?');
|
2014-09-28 18:05:46 +02:00
|
|
|
|
if ( pos !== -1 ) {
|
2016-04-16 17:20:01 +02:00
|
|
|
|
candidates.push(filter.slice(0, pos));
|
2014-09-28 18:05:46 +02:00
|
|
|
|
}
|
2015-03-19 15:13:51 +01:00
|
|
|
|
|
|
|
|
|
// Suggest a filter which is a result of combining more than one URL.
|
2016-04-16 17:20:01 +02:00
|
|
|
|
netFilterFromUnion(src, candidates);
|
|
|
|
|
|
|
|
|
|
return candidates.length - len;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const netFilter1stSources = {
|
2016-04-15 18:27:53 +02:00
|
|
|
|
'audio': 'src',
|
2015-02-10 16:50:47 +01:00
|
|
|
|
'embed': 'src',
|
2015-02-04 00:43:51 +01:00
|
|
|
|
'iframe': 'src',
|
|
|
|
|
'img': 'src',
|
2016-04-15 18:27:53 +02:00
|
|
|
|
'object': 'data',
|
|
|
|
|
'video': 'src'
|
2015-02-04 00:43:51 +01:00
|
|
|
|
};
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const filterTypes = {
|
2016-04-15 18:27:53 +02:00
|
|
|
|
'audio': 'media',
|
|
|
|
|
'embed': 'object',
|
|
|
|
|
'iframe': 'subdocument',
|
|
|
|
|
'img': 'image',
|
|
|
|
|
'object': 'object',
|
|
|
|
|
'video': 'media',
|
|
|
|
|
};
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
// Extract the best possible cosmetic filter, i.e. as specific as possible.
|
|
|
|
|
|
2016-06-16 16:55:49 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1725
|
2018-04-29 15:07:12 +02:00
|
|
|
|
// Also take into account the `src` attribute for `img` elements -- and limit
|
|
|
|
|
// the value to the 1024 first characters.
|
2016-06-16 16:55:49 +02:00
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const cosmeticFilterFromElement = function(elem) {
|
2018-04-29 15:07:12 +02:00
|
|
|
|
if ( elem === null ) { return 0; }
|
|
|
|
|
if ( elem.nodeType !== 1 ) { return 0; }
|
2016-04-16 17:20:01 +02:00
|
|
|
|
|
|
|
|
|
if ( candidateElements.indexOf(elem) === -1 ) {
|
|
|
|
|
candidateElements.push(elem);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let selector = '';
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
// Id
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let v = typeof elem.id === 'string' && CSS.escape(elem.id);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
if ( v ) {
|
2016-04-17 16:15:01 +02:00
|
|
|
|
selector = '#' + v;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Class(es)
|
2017-02-13 14:33:10 +01:00
|
|
|
|
v = elem.classList;
|
|
|
|
|
if ( v ) {
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let i = v.length || 0;
|
2017-02-13 14:33:10 +01:00
|
|
|
|
while ( i-- ) {
|
|
|
|
|
selector += '.' + CSS.escape(v.item(i));
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-20 23:38:28 +01:00
|
|
|
|
// Tag name
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const tagName = elem.localName;
|
2018-04-29 15:07:12 +02:00
|
|
|
|
|
|
|
|
|
// Use attributes if still no selector found.
|
2016-08-15 19:10:32 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1901
|
2018-04-29 15:07:12 +02:00
|
|
|
|
// Trim attribute value, this may help in case of malformed HTML.
|
2016-04-17 16:15:01 +02:00
|
|
|
|
if ( selector === '' ) {
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let attributes = [], attr;
|
2016-04-17 16:15:01 +02:00
|
|
|
|
switch ( tagName ) {
|
|
|
|
|
case 'a':
|
|
|
|
|
v = elem.getAttribute('href');
|
|
|
|
|
if ( v ) {
|
2016-08-15 19:10:32 +02:00
|
|
|
|
v = v.trim().replace(/\?.*$/, '');
|
2016-04-17 16:15:01 +02:00
|
|
|
|
if ( v.length ) {
|
|
|
|
|
attributes.push({ k: 'href', v: v });
|
|
|
|
|
}
|
2014-10-13 15:47:18 +02:00
|
|
|
|
}
|
2016-04-17 16:15:01 +02:00
|
|
|
|
break;
|
2016-08-21 15:49:11 +02:00
|
|
|
|
case 'iframe':
|
2016-04-17 16:15:01 +02:00
|
|
|
|
case 'img':
|
2016-06-16 16:55:49 +02:00
|
|
|
|
v = elem.getAttribute('src');
|
|
|
|
|
if ( v && v.length !== 0 ) {
|
2018-11-06 19:11:03 +01:00
|
|
|
|
v = v.trim();
|
|
|
|
|
if ( v.startsWith('data:') ) {
|
|
|
|
|
let pos = v.indexOf(',');
|
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
|
v = v.slice(0, pos + 1);
|
|
|
|
|
}
|
2018-11-06 20:22:09 +01:00
|
|
|
|
} else if ( v.startsWith('blob:') ) {
|
|
|
|
|
v = new URL(v.slice(5));
|
|
|
|
|
v.pathname = '';
|
|
|
|
|
v = 'blob:' + v.href;
|
2018-11-06 19:11:03 +01:00
|
|
|
|
}
|
|
|
|
|
attributes.push({ k: 'src', v: v.slice(0, 256) });
|
2016-06-16 16:55:49 +02:00
|
|
|
|
break;
|
|
|
|
|
}
|
2016-04-17 16:15:01 +02:00
|
|
|
|
v = elem.getAttribute('alt');
|
|
|
|
|
if ( v && v.length !== 0 ) {
|
|
|
|
|
attributes.push({ k: 'alt', v: v });
|
2016-06-16 16:55:49 +02:00
|
|
|
|
break;
|
2016-04-17 16:15:01 +02:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2016-04-17 16:15:01 +02:00
|
|
|
|
while ( (attr = attributes.pop()) ) {
|
2018-04-29 15:07:12 +02:00
|
|
|
|
if ( attr.v.length === 0 ) { continue; }
|
2016-04-17 16:15:01 +02:00
|
|
|
|
v = elem.getAttribute(attr.k);
|
|
|
|
|
if ( attr.v === v ) {
|
|
|
|
|
selector += '[' + attr.k + '="' + attr.v + '"]';
|
2018-04-29 15:07:12 +02:00
|
|
|
|
} else if ( v.startsWith(attr.v) ) {
|
2016-04-17 16:15:01 +02:00
|
|
|
|
selector += '[' + attr.k + '^="' + attr.v + '"]';
|
|
|
|
|
} else {
|
|
|
|
|
selector += '[' + attr.k + '*="' + attr.v + '"]';
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 15:07:12 +02:00
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/17
|
|
|
|
|
// If selector is ambiguous at this point, add the element name to
|
|
|
|
|
// further narrow it down.
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const parentNode = elem.parentNode;
|
2018-04-29 15:07:12 +02:00
|
|
|
|
if (
|
|
|
|
|
selector === '' ||
|
|
|
|
|
safeQuerySelectorAll(parentNode, cssScope + selector).length > 1
|
|
|
|
|
) {
|
|
|
|
|
selector = tagName + selector;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/637
|
2018-04-29 15:07:12 +02:00
|
|
|
|
// If the selector is still ambiguous at this point, further narrow using
|
|
|
|
|
// `nth-of-type`. It is preferable to use `nth-of-type` as opposed to
|
|
|
|
|
// `nth-child`, as `nth-of-type` is less volatile.
|
2015-02-16 17:21:25 +01:00
|
|
|
|
if ( safeQuerySelectorAll(parentNode, cssScope + selector).length > 1 ) {
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let i = 1;
|
2015-01-30 06:49:30 +01:00
|
|
|
|
while ( elem.previousSibling !== null ) {
|
|
|
|
|
elem = elem.previousSibling;
|
2018-04-29 15:07:12 +02:00
|
|
|
|
if (
|
|
|
|
|
typeof elem.localName === 'string' &&
|
|
|
|
|
elem.localName === tagName
|
|
|
|
|
) {
|
|
|
|
|
i++;
|
2015-01-30 06:49:30 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
selector += ':nth-of-type(' + i + ')';
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
if ( bestCandidateFilter === null ) {
|
|
|
|
|
bestCandidateFilter = {
|
2016-04-17 16:15:01 +02:00
|
|
|
|
type: 'cosmetic',
|
2016-04-16 17:20:01 +02:00
|
|
|
|
filters: cosmeticFilterCandidates,
|
|
|
|
|
slot: cosmeticFilterCandidates.length
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cosmeticFilterCandidates.push('##' + selector);
|
|
|
|
|
|
|
|
|
|
return 1;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const filtersFrom = function(x, y) {
|
2016-04-16 17:20:01 +02:00
|
|
|
|
bestCandidateFilter = null;
|
2014-09-28 18:05:46 +02:00
|
|
|
|
netFilterCandidates.length = 0;
|
|
|
|
|
cosmeticFilterCandidates.length = 0;
|
2016-04-16 17:20:01 +02:00
|
|
|
|
candidateElements.length = 0;
|
2016-04-15 18:27:53 +02:00
|
|
|
|
|
|
|
|
|
// We need at least one element.
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let first = null;
|
2016-04-15 18:27:53 +02:00
|
|
|
|
if ( typeof x === 'number' ) {
|
|
|
|
|
first = elementFromPoint(x, y);
|
|
|
|
|
} else if ( x instanceof HTMLElement ) {
|
|
|
|
|
first = x;
|
|
|
|
|
x = undefined;
|
|
|
|
|
}
|
2016-04-16 17:20:01 +02:00
|
|
|
|
|
|
|
|
|
// Network filter from element which was clicked.
|
|
|
|
|
if ( first !== null ) {
|
|
|
|
|
netFilterFromElement(first);
|
2016-04-15 18:27:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
// Cosmetic filter candidates from ancestors.
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let elem = first;
|
2015-03-20 23:38:28 +01:00
|
|
|
|
while ( elem && elem !== document.body ) {
|
2016-04-16 17:20:01 +02:00
|
|
|
|
cosmeticFilterFromElement(elem);
|
2014-09-28 18:05:46 +02:00
|
|
|
|
elem = elem.parentNode;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2015-03-20 23:38:28 +01:00
|
|
|
|
// The body tag is needed as anchor only when the immediate child
|
2018-04-29 15:07:12 +02:00
|
|
|
|
// uses `nth-of-type`.
|
|
|
|
|
let i = cosmeticFilterCandidates.length;
|
|
|
|
|
if ( i !== 0 ) {
|
|
|
|
|
let selector = cosmeticFilterCandidates[i-1];
|
|
|
|
|
if (
|
|
|
|
|
selector.indexOf(':nth-of-type(') !== -1 &&
|
|
|
|
|
safeQuerySelectorAll(document.body, selector).length > 1
|
|
|
|
|
) {
|
|
|
|
|
cosmeticFilterCandidates.push('##body');
|
|
|
|
|
}
|
2015-03-20 23:38:28 +01:00
|
|
|
|
}
|
2016-04-15 18:27:53 +02:00
|
|
|
|
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1545
|
2016-04-16 17:20:01 +02:00
|
|
|
|
// Network filter candidates from all other elements found at point (x, y).
|
2016-04-15 18:27:53 +02:00
|
|
|
|
if ( typeof x === 'number' ) {
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let attrName = pickerRoot.id + '-clickblind';
|
|
|
|
|
let previous;
|
2016-04-15 18:27:53 +02:00
|
|
|
|
elem = first;
|
2016-04-16 17:20:01 +02:00
|
|
|
|
while ( elem !== null ) {
|
2016-04-15 18:27:53 +02:00
|
|
|
|
previous = elem;
|
|
|
|
|
elem.setAttribute(attrName, '');
|
2016-04-16 17:20:01 +02:00
|
|
|
|
elem = elementFromPoint(x, y);
|
2016-04-15 18:27:53 +02:00
|
|
|
|
if ( elem === null || elem === previous ) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2016-04-16 17:20:01 +02:00
|
|
|
|
netFilterFromElement(elem);
|
2016-04-15 18:27:53 +02:00
|
|
|
|
}
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let elems = document.querySelectorAll('[' + attrName + ']');
|
2016-04-15 18:27:53 +02:00
|
|
|
|
i = elems.length;
|
|
|
|
|
while ( i-- ) {
|
|
|
|
|
elems[i].removeAttribute(attrName);
|
|
|
|
|
}
|
2016-04-16 17:20:01 +02:00
|
|
|
|
|
|
|
|
|
netFilterFromElement(document.body);
|
2016-04-15 18:27:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return netFilterCandidates.length + cosmeticFilterCandidates.length;
|
|
|
|
|
};
|
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
/*******************************************************************************
|
2016-04-15 18:27:53 +02:00
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
filterToDOMInterface.set
|
|
|
|
|
@desc Look-up all the HTML elements matching the filter passed in
|
|
|
|
|
argument.
|
2016-12-25 22:56:39 +01:00
|
|
|
|
@param string, a cosmetic or network filter.
|
|
|
|
|
@param function, called once all items matching the filter have been
|
|
|
|
|
collected.
|
2016-10-01 18:34:25 +02:00
|
|
|
|
@return array, or undefined if the filter is invalid.
|
2014-09-28 18:05:46 +02:00
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
filterToDOMInterface.preview
|
|
|
|
|
@desc Apply/unapply filter to the DOM.
|
|
|
|
|
@param string, a cosmetic of network filter, or literal false to remove
|
|
|
|
|
the effects of the filter on the DOM.
|
|
|
|
|
@return undefined.
|
2015-02-10 18:59:27 +01:00
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
TODO: need to be revised once I implement chained cosmetic operators.
|
|
|
|
|
|
|
|
|
|
*/
|
2019-06-29 17:06:03 +02:00
|
|
|
|
|
|
|
|
|
const filterToDOMInterface = (( ) => {
|
|
|
|
|
const reHnAnchorPrefix = '^[\\w-]+://(?:[^/?#]+\\.)?';
|
|
|
|
|
const reCaret = '(?:[^%.0-9a-z_-]|$)';
|
2019-08-11 22:32:49 +02:00
|
|
|
|
const rePseudoElements = /:(?::?after|:?before|:[a-z-]+)$/;
|
2019-06-29 17:06:03 +02:00
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
// Net filters: we need to lookup manually -- translating into a foolproof
|
|
|
|
|
// CSS selector is just not possible.
|
2019-06-29 17:06:03 +02:00
|
|
|
|
//
|
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/945
|
|
|
|
|
// Transform into a regular expression, this allows the user to
|
|
|
|
|
// edit and insert wildcard(s) into the proposed filter.
|
|
|
|
|
// https://www.reddit.com/r/uBlockOrigin/comments/c5do7w/
|
|
|
|
|
// Better handling of pure hostname filters. Also, discard single
|
|
|
|
|
// alphanumeric character filters.
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const fromNetworkFilter = function(filter) {
|
|
|
|
|
const out = [];
|
2019-06-29 17:06:03 +02:00
|
|
|
|
if ( /^[0-9a-z]$/i.test(filter) ) { return out; }
|
2019-02-18 22:00:42 +01:00
|
|
|
|
let reStr = '';
|
2019-06-29 17:06:03 +02:00
|
|
|
|
if (
|
|
|
|
|
filter.length > 2 &&
|
|
|
|
|
filter.startsWith('/') &&
|
|
|
|
|
filter.endsWith('/')
|
|
|
|
|
) {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
reStr = filter.slice(1, -1);
|
2019-06-29 17:06:03 +02:00
|
|
|
|
} else if ( /^\w[\w.-]*[a-z]$/i.test(filter) ) {
|
|
|
|
|
reStr = reHnAnchorPrefix +
|
|
|
|
|
filter.toLowerCase().replace(/\./g, '\\.') +
|
|
|
|
|
reCaret;
|
|
|
|
|
} else {
|
2019-02-18 22:00:42 +01:00
|
|
|
|
let rePrefix = '', reSuffix = '';
|
2019-06-29 17:06:03 +02:00
|
|
|
|
if ( filter.startsWith('||') ) {
|
|
|
|
|
rePrefix = reHnAnchorPrefix;
|
|
|
|
|
filter = filter.slice(2);
|
|
|
|
|
} else if ( filter.startsWith('|') ) {
|
|
|
|
|
rePrefix = '^';
|
|
|
|
|
filter = filter.slice(1);
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
2019-06-29 17:06:03 +02:00
|
|
|
|
if ( filter.endsWith('|') ) {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
reSuffix = '$';
|
|
|
|
|
filter = filter.slice(0, -1);
|
|
|
|
|
}
|
|
|
|
|
reStr = rePrefix +
|
2019-06-29 17:06:03 +02:00
|
|
|
|
filter.replace(/[.+?${}()|[\]\\]/g, '\\$&')
|
|
|
|
|
.replace(/\*+/g, '.*')
|
|
|
|
|
.replace(/\^/g, reCaret) +
|
2016-10-01 18:34:25 +02:00
|
|
|
|
reSuffix;
|
|
|
|
|
}
|
2019-02-18 22:00:42 +01:00
|
|
|
|
let reFilter = null;
|
2014-09-28 18:05:46 +02:00
|
|
|
|
try {
|
2020-02-24 15:24:54 +01:00
|
|
|
|
reFilter = new RegExp(reStr, 'i');
|
2014-09-28 18:05:46 +02:00
|
|
|
|
}
|
|
|
|
|
catch (e) {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
return out;
|
2016-04-15 18:27:53 +02:00
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
|
|
|
|
|
// Lookup by tag names.
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const elems = document.querySelectorAll(
|
2019-02-18 22:00:42 +01:00
|
|
|
|
Object.keys(netFilter1stSources).join()
|
|
|
|
|
);
|
|
|
|
|
for ( const elem of elems ) {
|
|
|
|
|
let srcProp = netFilter1stSources[elem.localName];
|
|
|
|
|
let src = elem[srcProp];
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if ( typeof src !== 'string' || src.length === 0 ) {
|
2019-04-14 23:37:48 +02:00
|
|
|
|
if (
|
|
|
|
|
typeof elem.srcset === 'string' &&
|
|
|
|
|
elem.srcset !== '' &&
|
|
|
|
|
typeof elem.currentSrc === 'string'
|
|
|
|
|
) {
|
|
|
|
|
src = elem.currentSrc;
|
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
|
|
|
|
if ( src && reFilter.test(src) ) {
|
|
|
|
|
out.push({
|
|
|
|
|
type: 'network',
|
|
|
|
|
elem: elem,
|
|
|
|
|
src: srcProp,
|
|
|
|
|
opts: filterTypes[elem.localName],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find matching background image in current set of candidate elements.
|
2019-02-18 22:00:42 +01:00
|
|
|
|
for ( const elem of candidateElements ) {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if ( reFilter.test(backgroundImageURLFromElement(elem)) ) {
|
|
|
|
|
out.push({
|
|
|
|
|
type: 'network',
|
|
|
|
|
elem: elem,
|
|
|
|
|
style: 'background-image',
|
|
|
|
|
opts: 'image',
|
|
|
|
|
});
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
return out;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
};
|
2014-09-28 18:05:46 +02:00
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
// Cosmetic filters: these are straight CSS selectors.
|
2019-02-18 22:00:42 +01:00
|
|
|
|
//
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/389
|
|
|
|
|
// Test filter using comma-separated list to better detect invalid CSS
|
|
|
|
|
// selectors.
|
2019-07-26 15:24:34 +02:00
|
|
|
|
//
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2515
|
|
|
|
|
// Remove trailing pseudo-element when querying.
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const fromPlainCosmeticFilter = function(raw) {
|
2018-11-06 19:11:03 +01:00
|
|
|
|
let elems;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
try {
|
2019-06-29 17:06:03 +02:00
|
|
|
|
document.documentElement.matches(`${raw},\na`);
|
2019-07-26 15:24:34 +02:00
|
|
|
|
elems = document.querySelectorAll(
|
|
|
|
|
raw.replace(rePseudoElements, '')
|
|
|
|
|
);
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
|
|
|
|
catch (e) {
|
2016-12-25 22:56:39 +01:00
|
|
|
|
return;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const out = [];
|
2018-11-06 19:20:44 +01:00
|
|
|
|
for ( const elem of elems ) {
|
2018-12-06 14:50:13 +01:00
|
|
|
|
if ( elem === pickerRoot ) { continue; }
|
2019-06-29 17:06:03 +02:00
|
|
|
|
out.push({ type: 'cosmetic', elem, raw });
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
};
|
2015-03-05 18:52:12 +01:00
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1772
|
2019-06-29 17:06:03 +02:00
|
|
|
|
// Handle procedural cosmetic filters.
|
2019-07-26 15:24:34 +02:00
|
|
|
|
//
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2515
|
|
|
|
|
// Remove trailing pseudo-element when querying.
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const fromCompiledCosmeticFilter = function(raw) {
|
2016-12-25 22:56:39 +01:00
|
|
|
|
if ( typeof raw !== 'string' ) { return; }
|
2019-07-26 15:24:34 +02:00
|
|
|
|
let elems;
|
2016-12-25 22:56:39 +01:00
|
|
|
|
try {
|
2019-07-26 15:24:34 +02:00
|
|
|
|
const o = JSON.parse(raw);
|
2020-03-07 20:25:06 +01:00
|
|
|
|
if ( o.action === 'style' ) {
|
2019-07-26 15:24:34 +02:00
|
|
|
|
elems = document.querySelectorAll(
|
2020-03-07 20:25:06 +01:00
|
|
|
|
o.selector.replace(rePseudoElements, '')
|
2019-07-26 15:24:34 +02:00
|
|
|
|
);
|
2020-03-07 20:25:06 +01:00
|
|
|
|
lastAction = o.selector + ' {' + o.tasks[0][1] + '}';
|
2019-07-26 15:24:34 +02:00
|
|
|
|
} else if ( o.tasks ) {
|
|
|
|
|
elems = vAPI.domFilterer.createProceduralFilter(o).exec();
|
|
|
|
|
}
|
2016-12-25 22:56:39 +01:00
|
|
|
|
} catch(ex) {
|
|
|
|
|
return;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
2016-12-25 22:56:39 +01:00
|
|
|
|
if ( !elems ) { return; }
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const out = [];
|
|
|
|
|
for ( const elem of elems ) {
|
2019-06-29 17:06:03 +02:00
|
|
|
|
out.push({ type: 'cosmetic', elem, raw });
|
2016-12-25 22:56:39 +01:00
|
|
|
|
}
|
|
|
|
|
return out;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
};
|
|
|
|
|
|
2019-02-18 22:00:42 +01:00
|
|
|
|
let lastFilter,
|
2016-10-01 18:34:25 +02:00
|
|
|
|
lastResultset,
|
|
|
|
|
lastAction,
|
|
|
|
|
appliedStyleTag,
|
|
|
|
|
applied = false,
|
|
|
|
|
previewing = false;
|
|
|
|
|
|
2019-09-17 21:15:01 +02:00
|
|
|
|
const queryAll = async function(filter, callback) {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
filter = filter.trim();
|
|
|
|
|
if ( filter === lastFilter ) {
|
2016-12-25 22:56:39 +01:00
|
|
|
|
callback(lastResultset);
|
|
|
|
|
return;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
|
|
|
|
unapply();
|
|
|
|
|
if ( filter === '' ) {
|
|
|
|
|
lastFilter = '';
|
|
|
|
|
lastResultset = [];
|
2016-12-25 22:56:39 +01:00
|
|
|
|
callback(lastResultset);
|
|
|
|
|
return;
|
2015-03-05 18:52:12 +01:00
|
|
|
|
}
|
2016-12-25 22:56:39 +01:00
|
|
|
|
lastFilter = filter;
|
|
|
|
|
lastAction = undefined;
|
2019-06-29 17:06:03 +02:00
|
|
|
|
if ( filter.startsWith('##') === false ) {
|
2016-12-25 22:56:39 +01:00
|
|
|
|
lastResultset = fromNetworkFilter(filter);
|
|
|
|
|
if ( previewing ) { apply(); }
|
|
|
|
|
callback(lastResultset);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const selector = filter.slice(2);
|
2016-12-25 22:56:39 +01:00
|
|
|
|
lastResultset = fromPlainCosmeticFilter(selector);
|
|
|
|
|
if ( lastResultset ) {
|
|
|
|
|
if ( previewing ) { apply(); }
|
|
|
|
|
callback(lastResultset);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Procedural cosmetic filter
|
2019-09-17 21:15:01 +02:00
|
|
|
|
const response = await vAPI.messaging.send('elementPicker', {
|
|
|
|
|
what: 'compileCosmeticFilterSelector',
|
|
|
|
|
selector,
|
|
|
|
|
});
|
|
|
|
|
lastResultset = fromCompiledCosmeticFilter(response);
|
|
|
|
|
if ( previewing ) { apply(); }
|
|
|
|
|
callback(lastResultset);
|
2016-10-01 18:34:25 +02:00
|
|
|
|
};
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1629
|
|
|
|
|
// Avoid hiding the element picker's related elements.
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const applyHide = function() {
|
|
|
|
|
const htmlElem = document.documentElement;
|
|
|
|
|
for ( const item of lastResultset ) {
|
|
|
|
|
const elem = item.elem;
|
|
|
|
|
if ( elem === pickerRoot ) { continue; }
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if (
|
|
|
|
|
(elem !== htmlElem) &&
|
|
|
|
|
(item.type === 'cosmetic' || item.type === 'network' && item.src !== undefined)
|
|
|
|
|
) {
|
2019-05-18 01:26:48 +02:00
|
|
|
|
vAPI.domFilterer.hideNode(elem);
|
|
|
|
|
item.hidden = true;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
|
|
|
|
if ( item.type === 'network' && item.style === 'background-image' ) {
|
2019-05-18 01:26:48 +02:00
|
|
|
|
const style = elem.style;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
item.backgroundImage = style.getPropertyValue('background-image');
|
|
|
|
|
item.backgroundImagePriority = style.getPropertyPriority('background-image');
|
|
|
|
|
style.setProperty('background-image', 'none', 'important');
|
|
|
|
|
}
|
2015-03-05 18:52:12 +01:00
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
};
|
2015-03-05 18:52:12 +01:00
|
|
|
|
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const unapplyHide = function() {
|
|
|
|
|
if ( lastResultset === undefined ) { return; }
|
|
|
|
|
for ( const item of lastResultset ) {
|
2019-05-18 01:26:48 +02:00
|
|
|
|
if ( item.hidden === true ) {
|
|
|
|
|
vAPI.domFilterer.unhideNode(item.elem);
|
|
|
|
|
item.hidden = false;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
|
|
|
|
if ( item.hasOwnProperty('backgroundImage') ) {
|
|
|
|
|
item.elem.style.setProperty(
|
|
|
|
|
'background-image',
|
|
|
|
|
item.backgroundImage,
|
|
|
|
|
item.backgroundImagePriority
|
|
|
|
|
);
|
|
|
|
|
delete item.backgroundImage;
|
|
|
|
|
}
|
2015-06-04 17:17:02 +02:00
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
};
|
|
|
|
|
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const unapplyStyle = function() {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if ( !appliedStyleTag || appliedStyleTag.parentNode === null ) {
|
|
|
|
|
return;
|
2016-04-15 18:27:53 +02:00
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
appliedStyleTag.parentNode.removeChild(appliedStyleTag);
|
|
|
|
|
};
|
2016-04-15 18:27:53 +02:00
|
|
|
|
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const applyStyle = function() {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if ( !appliedStyleTag ) {
|
|
|
|
|
appliedStyleTag = document.createElement('style');
|
|
|
|
|
appliedStyleTag.setAttribute('type', 'text/css');
|
2014-09-28 18:05:46 +02:00
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
appliedStyleTag.textContent = lastAction;
|
|
|
|
|
if ( appliedStyleTag.parentNode === null ) {
|
|
|
|
|
document.head.appendChild(appliedStyleTag);
|
|
|
|
|
}
|
|
|
|
|
};
|
2016-04-15 18:27:53 +02:00
|
|
|
|
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const apply = function() {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if ( applied ) {
|
|
|
|
|
unapply();
|
|
|
|
|
}
|
2019-02-18 22:00:42 +01:00
|
|
|
|
if ( lastResultset === undefined ) { return; }
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if ( typeof lastAction === 'string' ) {
|
|
|
|
|
applyStyle();
|
|
|
|
|
} else {
|
|
|
|
|
applyHide();
|
|
|
|
|
}
|
|
|
|
|
applied = true;
|
|
|
|
|
};
|
|
|
|
|
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const unapply = function() {
|
|
|
|
|
if ( !applied ) { return; }
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if ( typeof lastAction === 'string' ) {
|
|
|
|
|
unapplyStyle();
|
|
|
|
|
} else {
|
|
|
|
|
unapplyHide();
|
|
|
|
|
}
|
|
|
|
|
applied = false;
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
// https://www.reddit.com/r/uBlockOrigin/comments/c62irc/
|
|
|
|
|
// Support injecting the cosmetic filters into the DOM filterer
|
|
|
|
|
// immediately rather than wait for the next page load.
|
|
|
|
|
const preview = function(rawFilter, permanent = false) {
|
|
|
|
|
previewing = rawFilter !== false;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
pickerBody.classList.toggle('preview', previewing);
|
2019-06-29 17:06:03 +02:00
|
|
|
|
if ( previewing === false ) {
|
|
|
|
|
return unapply();
|
|
|
|
|
}
|
|
|
|
|
queryAll(rawFilter, items => {
|
|
|
|
|
if ( items === undefined ) { return; }
|
|
|
|
|
apply();
|
|
|
|
|
if ( permanent === false ) { return; }
|
|
|
|
|
if ( vAPI.domFilterer instanceof Object === false ) { return; }
|
|
|
|
|
const cssSelectors = new Set();
|
|
|
|
|
const proceduralSelectors = new Set();
|
|
|
|
|
for ( const item of items ) {
|
|
|
|
|
if ( item.type !== 'cosmetic' ) { continue; }
|
|
|
|
|
if ( item.raw.startsWith('{') ) {
|
|
|
|
|
proceduralSelectors.add(item.raw);
|
|
|
|
|
} else {
|
|
|
|
|
cssSelectors.add(item.raw);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-30 16:22:06 +02:00
|
|
|
|
if ( cssSelectors.size !== 0 ) {
|
|
|
|
|
vAPI.domFilterer.addCSSRule(
|
|
|
|
|
Array.from(cssSelectors),
|
|
|
|
|
'display:none!important;'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if ( proceduralSelectors.size !== 0 ) {
|
|
|
|
|
vAPI.domFilterer.addProceduralSelectors(
|
|
|
|
|
Array.from(proceduralSelectors)
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-06-29 17:06:03 +02:00
|
|
|
|
});
|
2016-10-01 18:34:25 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
previewing: function() { return previewing; },
|
|
|
|
|
preview: preview,
|
|
|
|
|
set: queryAll
|
|
|
|
|
};
|
|
|
|
|
})();
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-01-15 22:34:57 +01:00
|
|
|
|
const userFilterFromCandidate = function(callback) {
|
2019-02-18 22:00:42 +01:00
|
|
|
|
let v = rawFilterFromTextarea();
|
2019-06-29 17:06:03 +02:00
|
|
|
|
filterToDOMInterface.set(v, items => {
|
2016-12-25 22:56:39 +01:00
|
|
|
|
if ( !items || items.length === 0 ) {
|
|
|
|
|
callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/738
|
|
|
|
|
// Trim dots.
|
2019-02-18 22:00:42 +01:00
|
|
|
|
let hostname = window.location.hostname;
|
2016-12-25 22:56:39 +01:00
|
|
|
|
if ( hostname.slice(-1) === '.' ) {
|
|
|
|
|
hostname = hostname.slice(0, -1);
|
|
|
|
|
}
|
2015-11-24 21:27:39 +01:00
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
// Cosmetic filter?
|
2019-06-29 17:06:03 +02:00
|
|
|
|
if ( v.startsWith('##') ) {
|
2019-08-09 15:31:20 +02:00
|
|
|
|
callback(hostname + v, true);
|
2016-12-25 22:56:39 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
// Assume net filter
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const opts = [];
|
2016-04-15 18:27:53 +02:00
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
// If no domain included in filter, we need domain option
|
2019-06-29 17:06:03 +02:00
|
|
|
|
if ( v.startsWith('||') === false ) {
|
|
|
|
|
opts.push(`domain=${hostname}`);
|
2016-12-25 22:56:39 +01:00
|
|
|
|
}
|
2015-03-06 06:36:45 +01:00
|
|
|
|
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const item = items[0];
|
2016-12-25 22:56:39 +01:00
|
|
|
|
if ( item.opts ) {
|
|
|
|
|
opts.push(item.opts);
|
|
|
|
|
}
|
2016-04-15 18:27:53 +02:00
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
if ( opts.length ) {
|
|
|
|
|
v += '$' + opts.join(',');
|
|
|
|
|
}
|
2016-04-15 18:27:53 +02:00
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
callback(v);
|
|
|
|
|
});
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-01-15 22:34:57 +01:00
|
|
|
|
const onCandidateChanged = (function() {
|
2019-02-18 22:00:42 +01:00
|
|
|
|
const process = function(items) {
|
|
|
|
|
const elems = [];
|
|
|
|
|
const valid = items !== undefined;
|
2016-12-25 22:56:39 +01:00
|
|
|
|
if ( valid ) {
|
2019-02-18 22:00:42 +01:00
|
|
|
|
for ( const item of items ) {
|
|
|
|
|
elems.push(item.elem);
|
2016-12-25 22:56:39 +01:00
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
2017-02-27 22:29:36 +01:00
|
|
|
|
pickerBody.querySelector('#resultsetCount').textContent = valid ?
|
2016-12-25 22:56:39 +01:00
|
|
|
|
items.length.toLocaleString() :
|
2016-12-25 23:05:30 +01:00
|
|
|
|
'E';
|
2016-12-25 22:56:39 +01:00
|
|
|
|
dialog.querySelector('section').classList.toggle('invalidFilter', !valid);
|
|
|
|
|
dialog.querySelector('#create').disabled = elems.length === 0;
|
|
|
|
|
highlightElements(elems, true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return function() {
|
|
|
|
|
filterToDOMInterface.set(rawFilterFromTextarea(), process);
|
|
|
|
|
};
|
|
|
|
|
})();
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-01-15 22:34:57 +01:00
|
|
|
|
const candidateFromFilterChoice = function(filterChoice) {
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let slot = filterChoice.slot;
|
|
|
|
|
let filters = filterChoice.filters;
|
|
|
|
|
let filter = filters[slot];
|
2014-07-13 17:38:52 +02:00
|
|
|
|
|
2018-04-29 15:07:12 +02:00
|
|
|
|
if ( filter === undefined ) { return ''; }
|
2014-07-13 17:38:52 +02:00
|
|
|
|
|
|
|
|
|
// For net filters there no such thing as a path
|
2018-04-29 15:07:12 +02:00
|
|
|
|
if ( filter.startsWith('##') === false ) { return filter; }
|
2014-07-13 17:38:52 +02:00
|
|
|
|
|
2016-04-16 19:21:48 +02:00
|
|
|
|
// At this point, we have a cosmetic filter
|
|
|
|
|
|
|
|
|
|
// Modifier means "target broadly". Hence:
|
|
|
|
|
// - Do not compute exact path.
|
|
|
|
|
// - Discard narrowing directives.
|
2018-04-29 15:07:12 +02:00
|
|
|
|
// - Remove the id if one or more classes exist
|
|
|
|
|
// TODO: should remove tag name too? ¯\_(ツ)_/¯
|
2016-04-16 19:21:48 +02:00
|
|
|
|
if ( filterChoice.modifier ) {
|
2017-02-13 14:33:10 +01:00
|
|
|
|
filter = filter.replace(/:nth-of-type\(\d+\)/, '');
|
2018-08-13 01:58:33 +02:00
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/162
|
|
|
|
|
// Mind escaped periods: they do not denote a class identifier.
|
|
|
|
|
if ( filter.charAt(2) === '#' ) {
|
|
|
|
|
let pos = filter.search(/[^\\]\./);
|
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
|
filter = '##' + filter.slice(pos + 1);
|
2018-04-29 15:07:12 +02:00
|
|
|
|
}
|
2017-02-13 14:33:10 +01:00
|
|
|
|
}
|
|
|
|
|
return filter;
|
2016-04-16 19:21:48 +02:00
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
|
2014-07-13 17:38:52 +02:00
|
|
|
|
// Return path: the target element, then all siblings prepended
|
2018-04-29 15:07:12 +02:00
|
|
|
|
let selector = '', joiner = '';
|
2014-09-28 18:05:46 +02:00
|
|
|
|
for ( ; slot < filters.length; slot++ ) {
|
|
|
|
|
filter = filters[slot];
|
2017-02-13 14:33:10 +01:00
|
|
|
|
// Remove all classes when an id exists.
|
2018-08-13 01:58:33 +02:00
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/162
|
|
|
|
|
// Mind escaped periods: they do not denote a class identifier.
|
2017-02-13 14:33:10 +01:00
|
|
|
|
if ( filter.charAt(2) === '#' ) {
|
2018-08-13 01:58:33 +02:00
|
|
|
|
filter = filter.replace(/([^\\])\..+$/, '$1');
|
2017-02-13 14:33:10 +01:00
|
|
|
|
}
|
2016-10-01 18:34:25 +02:00
|
|
|
|
selector = filter.slice(2) + joiner + selector;
|
2014-09-26 00:10:58 +02:00
|
|
|
|
// Stop at any element with an id: these are unique in a web page
|
2018-04-29 15:07:12 +02:00
|
|
|
|
if ( filter.startsWith('###') ) { break; }
|
2016-10-01 18:34:25 +02:00
|
|
|
|
// Stop if current selector matches only one element on the page
|
2018-04-29 15:07:12 +02:00
|
|
|
|
if ( document.querySelectorAll(selector).length === 1 ) { break; }
|
2016-10-01 18:34:25 +02:00
|
|
|
|
joiner = ' > ';
|
2014-07-13 17:38:52 +02:00
|
|
|
|
}
|
2017-04-08 16:40:38 +02:00
|
|
|
|
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2519
|
2018-04-29 15:07:12 +02:00
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/17
|
2017-04-08 16:40:38 +02:00
|
|
|
|
if (
|
|
|
|
|
slot === filters.length &&
|
2018-04-29 15:07:12 +02:00
|
|
|
|
selector.startsWith('body > ') === false &&
|
2017-04-08 16:40:38 +02:00
|
|
|
|
document.querySelectorAll(selector).length > 1
|
|
|
|
|
) {
|
|
|
|
|
selector = 'body > ' + selector;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
return '##' + selector;
|
2014-07-13 17:38:52 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-01-15 22:34:57 +01:00
|
|
|
|
const filterChoiceFromEvent = function(ev) {
|
2019-06-29 17:06:03 +02:00
|
|
|
|
let li = ev.target;
|
|
|
|
|
const isNetFilter = li.textContent.startsWith('##') === false;
|
|
|
|
|
const r = {
|
2014-09-28 18:05:46 +02:00
|
|
|
|
filters: isNetFilter ? netFilterCandidates : cosmeticFilterCandidates,
|
|
|
|
|
slot: 0,
|
|
|
|
|
modifier: ev.ctrlKey || ev.metaKey
|
|
|
|
|
};
|
|
|
|
|
while ( li.previousSibling !== null ) {
|
|
|
|
|
li = li.previousSibling;
|
|
|
|
|
r.slot += 1;
|
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-01-08 13:37:50 +01:00
|
|
|
|
const onDialogClicked = function(ev) {
|
2017-11-12 13:44:28 +01:00
|
|
|
|
if ( ev.isTrusted === false ) { return; }
|
2017-10-28 15:07:41 +02:00
|
|
|
|
|
|
|
|
|
// If the dialog is hidden, clicking on it force it to become visible.
|
|
|
|
|
if ( dialog.classList.contains('hide') ) {
|
|
|
|
|
dialog.classList.add('show');
|
|
|
|
|
dialog.classList.remove('hide');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( ev.target === null ) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
/* do nothing */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( ev.target.id === 'create' ) {
|
2016-04-16 17:20:01 +02:00
|
|
|
|
// We have to exit from preview mode: this guarantees matching elements
|
|
|
|
|
// will be found for the candidate filter.
|
2016-10-01 18:34:25 +02:00
|
|
|
|
filterToDOMInterface.preview(false);
|
2019-08-09 15:31:20 +02:00
|
|
|
|
userFilterFromCandidate((filter = undefined, isCosmetic = false) => {
|
|
|
|
|
if ( filter === undefined ) { return; }
|
2019-09-17 21:15:01 +02:00
|
|
|
|
vAPI.messaging.send('elementPicker', {
|
|
|
|
|
what: 'createUserFilter',
|
|
|
|
|
autoComment: true,
|
|
|
|
|
filters: filter,
|
|
|
|
|
origin: window.location.origin,
|
|
|
|
|
pageDomain: window.location.hostname,
|
|
|
|
|
killCache: isCosmetic === false,
|
|
|
|
|
});
|
2019-06-29 17:06:03 +02:00
|
|
|
|
filterToDOMInterface.preview(rawFilterFromTextarea(), true);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
stopPicker();
|
2016-12-25 22:56:39 +01:00
|
|
|
|
});
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( ev.target.id === 'pick' ) {
|
|
|
|
|
unpausePicker();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( ev.target.id === 'quit' ) {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
filterToDOMInterface.preview(false);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
stopPicker();
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
else if ( ev.target.id === 'preview' ) {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if ( filterToDOMInterface.previewing() ) {
|
|
|
|
|
filterToDOMInterface.preview(false);
|
2016-04-16 17:20:01 +02:00
|
|
|
|
} else {
|
2016-12-25 22:56:39 +01:00
|
|
|
|
filterToDOMInterface.preview(rawFilterFromTextarea());
|
2016-04-16 17:20:01 +02:00
|
|
|
|
}
|
|
|
|
|
highlightElements(targetElements, true);
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-08 19:34:28 +01:00
|
|
|
|
else if ( ev.target.parentNode.classList.contains('changeFilter') ) {
|
2014-09-28 18:05:46 +02:00
|
|
|
|
taCandidate.value = candidateFromFilterChoice(filterChoiceFromEvent(ev));
|
2014-07-13 02:32:44 +02:00
|
|
|
|
onCandidateChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-01-15 22:34:57 +01:00
|
|
|
|
const removeAllChildren = function(parent) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
while ( parent.firstChild ) {
|
|
|
|
|
parent.removeChild(parent.firstChild);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2018-12-22 21:44:23 +01:00
|
|
|
|
const showDialog = function(options) {
|
2014-09-28 18:05:46 +02:00
|
|
|
|
pausePicker();
|
|
|
|
|
|
|
|
|
|
options = options || {};
|
|
|
|
|
|
2017-10-28 15:07:41 +02:00
|
|
|
|
// Typically the dialog will be forced to be visible when using a
|
|
|
|
|
// touch-aware device.
|
|
|
|
|
dialog.classList.toggle('show', options.show === true);
|
|
|
|
|
dialog.classList.remove('hide');
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
// Create lists of candidate filters
|
2018-12-22 21:44:23 +01:00
|
|
|
|
const populate = function(src, des) {
|
2019-01-15 22:34:57 +01:00
|
|
|
|
const root = dialog.querySelector(des);
|
|
|
|
|
const ul = root.querySelector('ul');
|
2014-09-28 18:05:46 +02:00
|
|
|
|
removeAllChildren(ul);
|
2019-01-15 22:34:57 +01:00
|
|
|
|
for ( let i = 0; i < src.length; i++ ) {
|
|
|
|
|
const li = document.createElement('li');
|
2014-09-28 18:05:46 +02:00
|
|
|
|
li.textContent = src[i];
|
|
|
|
|
ul.appendChild(li);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2019-01-15 22:34:57 +01:00
|
|
|
|
if ( src.length !== 0 ) {
|
|
|
|
|
root.style.removeProperty('display');
|
|
|
|
|
} else {
|
|
|
|
|
root.style.setProperty('display', 'none', 'important');
|
|
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
|
};
|
|
|
|
|
|
2014-09-28 22:11:32 +02:00
|
|
|
|
populate(netFilterCandidates, '#netFilters');
|
|
|
|
|
populate(cosmeticFilterCandidates, '#cosmeticFilters');
|
2014-09-28 18:05:46 +02:00
|
|
|
|
|
2017-05-27 17:51:24 +02:00
|
|
|
|
dialog.querySelector('ul').style.display =
|
|
|
|
|
netFilterCandidates.length || cosmeticFilterCandidates.length
|
|
|
|
|
? ''
|
|
|
|
|
: 'none';
|
2015-02-09 14:03:29 +01:00
|
|
|
|
dialog.querySelector('#create').disabled = true;
|
2014-09-28 18:05:46 +02:00
|
|
|
|
|
|
|
|
|
// Auto-select a candidate filter
|
2016-04-16 17:20:01 +02:00
|
|
|
|
|
|
|
|
|
if ( bestCandidateFilter === null ) {
|
|
|
|
|
taCandidate.value = '';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-22 21:44:23 +01:00
|
|
|
|
const filterChoice = {
|
2016-04-16 17:20:01 +02:00
|
|
|
|
filters: bestCandidateFilter.filters,
|
|
|
|
|
slot: bestCandidateFilter.slot,
|
2014-09-28 18:05:46 +02:00
|
|
|
|
modifier: options.modifier || false
|
|
|
|
|
};
|
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
taCandidate.value = candidateFromFilterChoice(filterChoice);
|
|
|
|
|
onCandidateChanged();
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-05-06 19:32:55 +02:00
|
|
|
|
// https://www.reddit.com/r/uBlockOrigin/comments/bktxtb/scrolling_doesnt_work/emn901o
|
|
|
|
|
// Override 'fixed' position property on body element if present.
|
|
|
|
|
|
2018-12-22 21:44:23 +01:00
|
|
|
|
const zap = function() {
|
2017-05-27 17:51:24 +02:00
|
|
|
|
if ( targetElements.length === 0 ) { return; }
|
2019-05-06 19:32:55 +02:00
|
|
|
|
|
|
|
|
|
const getStyleValue = function(elem, prop) {
|
|
|
|
|
const style = window.getComputedStyle(elem);
|
|
|
|
|
return style ? style[prop] : '';
|
|
|
|
|
};
|
|
|
|
|
|
2018-12-22 21:44:23 +01:00
|
|
|
|
let elem = targetElements[0];
|
2017-05-27 17:51:24 +02:00
|
|
|
|
// Heuristic to detect scroll-locking: remove such lock when detected.
|
2019-05-06 19:32:55 +02:00
|
|
|
|
if (
|
|
|
|
|
parseInt(getStyleValue(elem, 'zIndex'), 10) >= 1000 ||
|
|
|
|
|
getStyleValue(elem, 'position') === 'fixed'
|
|
|
|
|
) {
|
|
|
|
|
const doc = document;
|
|
|
|
|
if ( getStyleValue(doc.body, 'overflowY') === 'hidden' ) {
|
|
|
|
|
doc.body.style.setProperty('overflow', 'auto', 'important');
|
|
|
|
|
}
|
|
|
|
|
if ( getStyleValue(doc.body, 'position') === 'fixed' ) {
|
|
|
|
|
doc.body.style.setProperty('position', 'static', 'important');
|
|
|
|
|
}
|
|
|
|
|
if ( getStyleValue(doc.documentElement, 'overflowY') === 'hidden' ) {
|
|
|
|
|
doc.documentElement.style.setProperty('overflow', 'auto', 'important');
|
|
|
|
|
}
|
2015-12-03 07:08:37 +01:00
|
|
|
|
}
|
2019-05-06 19:32:55 +02:00
|
|
|
|
|
2017-05-27 17:51:24 +02:00
|
|
|
|
elem.parentNode.removeChild(elem);
|
2017-05-28 19:45:11 +02:00
|
|
|
|
elem = elementFromPoint();
|
2019-05-06 19:32:55 +02:00
|
|
|
|
highlightElements(elem ? [ elem ] : []);
|
2014-09-28 20:38:17 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const elementFromPoint = (( ) => {
|
|
|
|
|
let lastX, lastY;
|
2017-05-27 17:51:24 +02:00
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
return (x, y) => {
|
2017-05-27 17:51:24 +02:00
|
|
|
|
if ( x !== undefined ) {
|
|
|
|
|
lastX = x; lastY = y;
|
|
|
|
|
} else if ( lastX !== undefined ) {
|
|
|
|
|
x = lastX; y = lastY;
|
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if ( !pickerRoot ) { return null; }
|
2019-01-15 22:34:57 +01:00
|
|
|
|
pickerRoot.style.setProperty('pointer-events', 'none', 'important');
|
2019-06-29 17:06:03 +02:00
|
|
|
|
let elem = document.elementFromPoint(x, y);
|
2017-05-27 17:51:24 +02:00
|
|
|
|
if ( elem === document.body || elem === document.documentElement ) {
|
|
|
|
|
elem = null;
|
|
|
|
|
}
|
2019-01-15 22:34:57 +01:00
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/380
|
|
|
|
|
pickerRoot.style.setProperty('pointer-events', 'auto', 'important');
|
2017-05-27 17:51:24 +02:00
|
|
|
|
return elem;
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-01-15 22:34:57 +01:00
|
|
|
|
const onSvgHovered = (function() {
|
|
|
|
|
let timer;
|
|
|
|
|
let mx = 0, my = 0;
|
2015-05-27 21:15:20 +02:00
|
|
|
|
|
2019-01-15 22:34:57 +01:00
|
|
|
|
const onTimer = function() {
|
|
|
|
|
timer = undefined;
|
|
|
|
|
const elem = elementFromPoint(mx, my);
|
2015-05-27 21:15:20 +02:00
|
|
|
|
highlightElements(elem ? [elem] : []);
|
|
|
|
|
};
|
|
|
|
|
|
2017-05-29 16:38:22 +02:00
|
|
|
|
return function onMove(ev) {
|
2015-05-30 19:44:55 +02:00
|
|
|
|
mx = ev.clientX;
|
|
|
|
|
my = ev.clientY;
|
2019-01-15 22:34:57 +01:00
|
|
|
|
if ( timer === undefined ) {
|
2015-05-30 19:44:55 +02:00
|
|
|
|
timer = vAPI.setTimeout(onTimer, 40);
|
2015-05-27 21:15:20 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
2017-05-29 16:38:22 +02:00
|
|
|
|
})();
|
|
|
|
|
|
2017-10-28 15:07:41 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
*/
|
2015-05-27 21:15:20 +02:00
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const onSvgTouchStartStop = (function() {
|
2017-05-29 16:38:22 +02:00
|
|
|
|
var startX,
|
|
|
|
|
startY;
|
|
|
|
|
return function onTouch(ev) {
|
|
|
|
|
if ( ev.type === 'touchstart' ) {
|
2017-10-31 13:26:51 +01:00
|
|
|
|
startX = ev.touches[0].screenX;
|
|
|
|
|
startY = ev.touches[0].screenY;
|
2017-05-29 16:38:22 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( startX === undefined ) { return; }
|
2017-10-31 13:26:51 +01:00
|
|
|
|
if ( ev.cancelable === false ) { return; }
|
|
|
|
|
var stopX = ev.changedTouches[0].screenX,
|
|
|
|
|
stopY = ev.changedTouches[0].screenY,
|
2017-10-28 15:07:41 +02:00
|
|
|
|
angle = Math.abs(Math.atan2(stopY - startY, stopX - startX)),
|
2017-05-29 16:38:22 +02:00
|
|
|
|
distance = Math.sqrt(
|
|
|
|
|
Math.pow(stopX - startX, 2),
|
|
|
|
|
Math.pow(stopY - startY, 2)
|
|
|
|
|
);
|
2017-10-28 15:07:41 +02:00
|
|
|
|
// Interpret touch events as a click events if swipe is not valid.
|
2017-10-31 13:26:51 +01:00
|
|
|
|
if ( distance < 32 ) {
|
2017-10-28 15:07:41 +02:00
|
|
|
|
onSvgClicked({
|
|
|
|
|
type: 'touch',
|
|
|
|
|
target: ev.target,
|
2017-10-31 13:26:51 +01:00
|
|
|
|
clientX: ev.changedTouches[0].pageX,
|
2017-11-12 13:44:28 +01:00
|
|
|
|
clientY: ev.changedTouches[0].pageY,
|
|
|
|
|
isTrusted: ev.isTrusted
|
2017-10-28 15:07:41 +02:00
|
|
|
|
});
|
2017-10-31 13:26:51 +01:00
|
|
|
|
ev.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( distance < 64 ) { return; }
|
|
|
|
|
var angleUpperBound = Math.PI * 0.25 * 0.5,
|
|
|
|
|
swipeRight = angle < angleUpperBound;
|
|
|
|
|
if ( swipeRight === false && angle < Math.PI - angleUpperBound ) {
|
2017-05-29 16:38:22 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2017-10-31 13:26:51 +01:00
|
|
|
|
ev.preventDefault();
|
2017-10-28 15:07:41 +02:00
|
|
|
|
// Swipe left.
|
|
|
|
|
if ( swipeRight === false ) {
|
|
|
|
|
if ( pickerBody.classList.contains('paused') ) {
|
|
|
|
|
dialog.classList.remove('hide');
|
|
|
|
|
dialog.classList.add('show');
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Swipe right.
|
|
|
|
|
if (
|
|
|
|
|
pickerBody.classList.contains('paused') &&
|
|
|
|
|
dialog.classList.contains('show')
|
|
|
|
|
) {
|
|
|
|
|
dialog.classList.remove('show');
|
|
|
|
|
dialog.classList.add('hide');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
stopPicker();
|
2017-05-29 16:38:22 +02:00
|
|
|
|
};
|
2015-05-27 21:15:20 +02:00
|
|
|
|
})();
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const onSvgClicked = function(ev) {
|
2017-11-12 13:44:28 +01:00
|
|
|
|
if ( ev.isTrusted === false ) { return; }
|
|
|
|
|
|
2017-05-29 16:38:22 +02:00
|
|
|
|
// If zap mode, highlight element under mouse, this makes the zapper usable
|
|
|
|
|
// on touch screens.
|
|
|
|
|
if ( pickerBody.classList.contains('zap') ) {
|
|
|
|
|
var elem = targetElements.lenght !== 0 && targetElements[0];
|
|
|
|
|
if ( !elem || ev.target !== svgIslands ) {
|
|
|
|
|
elem = elementFromPoint(ev.clientX, ev.clientY);
|
|
|
|
|
if ( elem !== null ) {
|
|
|
|
|
highlightElements([elem]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
zap();
|
|
|
|
|
if ( !ev.shiftKey ) {
|
|
|
|
|
stopPicker();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-04-07 03:26:05 +02:00
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/810#issuecomment-74600694
|
2016-10-01 18:34:25 +02:00
|
|
|
|
// Unpause picker if:
|
|
|
|
|
// - click outside dialog AND
|
|
|
|
|
// - not in preview mode
|
2016-04-16 17:20:01 +02:00
|
|
|
|
if ( pickerBody.classList.contains('paused') ) {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
if ( filterToDOMInterface.previewing() === false ) {
|
|
|
|
|
unpausePicker();
|
|
|
|
|
}
|
2015-03-09 15:11:22 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
2016-04-15 18:27:53 +02:00
|
|
|
|
if ( filtersFrom(ev.clientX, ev.clientY) === 0 ) {
|
2014-09-28 20:38:17 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2018-12-22 21:44:23 +01:00
|
|
|
|
showDialog({
|
|
|
|
|
show: ev.type === 'touch',
|
|
|
|
|
modifier: ev.ctrlKey
|
|
|
|
|
});
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const svgListening = function(on) {
|
2015-02-09 14:03:29 +01:00
|
|
|
|
var action = (on ? 'add' : 'remove') + 'EventListener';
|
2017-05-29 16:38:22 +02:00
|
|
|
|
svgRoot[action]('mousemove', onSvgHovered, { passive: true });
|
2015-02-09 14:03:29 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const onKeyPressed = function(ev) {
|
2017-05-27 17:51:24 +02:00
|
|
|
|
// Delete
|
In Element Zapper, support Mac keyboards’ Delete key (#3770)
Override the Backspace key, not just the Delete key, as Mac keyboards have Backspace as the only delete key and label it Delete.
Source of key value: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values#Editing_keys
More background on Mac keyboard shortcuts: Mac keyboards can emulate Delete using fn+Delete, meaning Forward Delete, but Mac software does not use it except in text editing. When deletion is dangerous, Mac software requires holding a modifier key in conjuction with Delete, but I think it’s better to make deletion easy in this case.
This new binding has a potential downside: if the user Backspace key normally goes Back in history (which can differ across OSs and browsers), this will change the behavior to delete the selected element instead. If the user really wants to go back in history, they will have to press Escape to leave the mode and then press Backspace, or they will have to press an alternative keyboard shortcuts such as Alt+Left. I think the user will rarely want to go back in history in the middle of picking an element, though.
That downside could be mitigated by conditioning the key check on `runtime.PlatformOs` (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/PlatformOs). But that would complicate the code a lot compared to the importance of this feature, and such detection would still fail to capture the user’s intent accurately in all cases. I think it’s better to unconditionally accept both Backspace (Delete) and Delete (Forward Delete).
2020-02-28 20:28:00 +01:00
|
|
|
|
if (
|
|
|
|
|
(ev.key === 'Delete' || ev.key === 'Backspace') &&
|
|
|
|
|
pickerBody.classList.contains('zap')
|
|
|
|
|
) {
|
2017-05-27 17:51:24 +02:00
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
zap();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Esc
|
|
|
|
|
if ( ev.key === 'Escape' || ev.which === 27 ) {
|
2014-07-13 17:38:52 +02:00
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
ev.preventDefault();
|
2016-12-25 22:56:39 +01:00
|
|
|
|
filterToDOMInterface.preview(false);
|
2014-07-13 17:38:52 +02:00
|
|
|
|
stopPicker();
|
2017-05-27 17:51:24 +02:00
|
|
|
|
return;
|
2014-07-13 17:38:52 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/190
|
2014-08-30 23:22:31 +02:00
|
|
|
|
// May need to dynamically adjust the height of the overlay + new position
|
|
|
|
|
// of highlighted elements.
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const onScrolled = function() {
|
2014-08-30 23:20:14 +02:00
|
|
|
|
highlightElements(targetElements, true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-11-05 18:03:48 +01:00
|
|
|
|
const onStartMoving = (( ) => {
|
|
|
|
|
let mx0 = 0, my0 = 0;
|
2019-11-06 13:01:13 +01:00
|
|
|
|
let mx1 = 0, my1 = 0;
|
2019-11-05 18:03:48 +01:00
|
|
|
|
let r0 = 0, b0 = 0;
|
|
|
|
|
let rMax = 0, bMax = 0;
|
2019-11-06 13:01:13 +01:00
|
|
|
|
let timer;
|
2019-11-05 18:03:48 +01:00
|
|
|
|
|
2019-11-06 13:01:13 +01:00
|
|
|
|
const move = ( ) => {
|
|
|
|
|
timer = undefined;
|
|
|
|
|
let r1 = Math.min(Math.max(r0 - mx1 + mx0, 4), rMax);
|
|
|
|
|
let b1 = Math.min(Math.max(b0 - my1 + my0, 4), bMax);
|
2019-11-05 18:03:48 +01:00
|
|
|
|
dialog.style.setProperty('right', `${r1}px`, 'important');
|
|
|
|
|
dialog.style.setProperty('bottom', `${b1}px`, 'important');
|
2019-11-06 13:01:13 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const moveAsync = ev => {
|
|
|
|
|
if ( ev.isTrusted === false ) { return; }
|
2019-11-05 18:03:48 +01:00
|
|
|
|
ev.preventDefault();
|
|
|
|
|
ev.stopPropagation();
|
2019-11-06 13:01:13 +01:00
|
|
|
|
if ( timer !== undefined ) { return; }
|
|
|
|
|
mx1 = ev.pageX;
|
|
|
|
|
my1 = ev.pageY;
|
|
|
|
|
timer = self.requestAnimationFrame(move);
|
2019-11-05 18:03:48 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const stop = ev => {
|
|
|
|
|
if ( ev.isTrusted === false ) { return; }
|
|
|
|
|
if ( dialog.classList.contains('moving') === false ) { return; }
|
|
|
|
|
dialog.classList.remove('moving');
|
|
|
|
|
const pickerWin = pickerRoot.contentWindow;
|
2019-11-06 13:01:13 +01:00
|
|
|
|
pickerWin.removeEventListener('mousemove', moveAsync, { capture: true });
|
2019-11-05 18:03:48 +01:00
|
|
|
|
pickerWin.removeEventListener('mouseup', stop, { capture: true, once: true });
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return function(ev) {
|
|
|
|
|
if ( ev.isTrusted === false ) { return; }
|
|
|
|
|
const target = dialog.querySelector('#toolbar');
|
|
|
|
|
if ( ev.target !== target ) { return; }
|
|
|
|
|
if ( dialog.classList.contains('moving') ) { return; }
|
|
|
|
|
mx0 = ev.pageX; my0 = ev.pageY;
|
|
|
|
|
const pickerWin = pickerRoot.contentWindow;
|
|
|
|
|
const style = pickerWin.getComputedStyle(dialog);
|
|
|
|
|
r0 = parseInt(style.right, 10);
|
|
|
|
|
b0 = parseInt(style.bottom, 10);
|
|
|
|
|
const rect = dialog.getBoundingClientRect();
|
|
|
|
|
rMax = pickerBody.clientWidth - 4 - rect.width ;
|
|
|
|
|
bMax = pickerBody.clientHeight - 4 - rect.height;
|
|
|
|
|
dialog.classList.add('moving');
|
2019-11-06 13:01:13 +01:00
|
|
|
|
pickerWin.addEventListener('mousemove', moveAsync, { capture: true });
|
2019-11-05 18:03:48 +01:00
|
|
|
|
pickerWin.addEventListener('mouseup', stop, { capture: true, once: true });
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const pausePicker = function() {
|
2016-04-16 17:20:01 +02:00
|
|
|
|
pickerBody.classList.add('paused');
|
2015-03-09 15:11:22 +01:00
|
|
|
|
svgListening(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const unpausePicker = function() {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
filterToDOMInterface.preview(false);
|
2016-04-16 17:20:01 +02:00
|
|
|
|
pickerBody.classList.remove('paused');
|
2015-03-09 15:11:22 +01:00
|
|
|
|
svgListening(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
// Let's have the element picker code flushed from memory when no longer
|
|
|
|
|
// in use: to ensure this, release all local references.
|
|
|
|
|
|
2019-06-29 17:06:03 +02:00
|
|
|
|
const stopPicker = function() {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
vAPI.shutdown.remove(stopPicker);
|
|
|
|
|
|
2014-07-13 17:38:52 +02:00
|
|
|
|
targetElements = [];
|
2016-04-16 17:20:01 +02:00
|
|
|
|
candidateElements = [];
|
|
|
|
|
bestCandidateFilter = null;
|
2015-02-09 14:03:29 +01:00
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
|
if ( pickerRoot === null ) { return; }
|
2015-02-09 14:03:29 +01:00
|
|
|
|
|
2016-10-08 21:14:24 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2060
|
2017-10-21 19:43:46 +02:00
|
|
|
|
if ( vAPI.domFilterer instanceof Object ) {
|
2017-10-22 14:59:29 +02:00
|
|
|
|
vAPI.userStylesheet.remove(pickerCSS1);
|
|
|
|
|
vAPI.userStylesheet.remove(pickerCSS2);
|
|
|
|
|
vAPI.userStylesheet.apply();
|
2016-10-08 21:14:24 +02:00
|
|
|
|
}
|
2017-10-22 14:59:29 +02:00
|
|
|
|
vAPI.domFilterer.unexcludeNode(pickerRoot);
|
2016-10-08 21:14:24 +02:00
|
|
|
|
|
2015-02-09 14:03:29 +01:00
|
|
|
|
window.removeEventListener('scroll', onScrolled, true);
|
|
|
|
|
svgListening(false);
|
|
|
|
|
pickerRoot.parentNode.removeChild(pickerRoot);
|
2019-11-05 18:03:48 +01:00
|
|
|
|
pickerRoot = pickerBody =
|
|
|
|
|
svgRoot = svgOcean = svgIslands =
|
|
|
|
|
dialog = taCandidate = null;
|
2015-02-09 14:03:29 +01:00
|
|
|
|
|
|
|
|
|
window.focus();
|
2014-07-13 17:38:52 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const startPicker = function(details) {
|
2017-10-04 17:14:24 +02:00
|
|
|
|
pickerRoot.addEventListener('load', stopPicker);
|
2015-02-11 17:49:50 +01:00
|
|
|
|
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const frameDoc = pickerRoot.contentDocument;
|
|
|
|
|
const parsedDom = (new DOMParser()).parseFromString(
|
2015-02-08 19:34:28 +01:00
|
|
|
|
details.frameContent,
|
|
|
|
|
'text/html'
|
|
|
|
|
);
|
2015-02-09 14:03:29 +01:00
|
|
|
|
|
2015-10-30 18:04:01 +01:00
|
|
|
|
// Provide an id users can use as anchor to personalize uBO's element
|
|
|
|
|
// picker style properties.
|
|
|
|
|
parsedDom.documentElement.id = 'ublock0-epicker';
|
|
|
|
|
|
2018-08-15 13:58:42 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2240
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/170
|
|
|
|
|
// Remove the already declared inline style tag: we will create a new
|
|
|
|
|
// one based on the removed one, and replace the old one.
|
|
|
|
|
let style = parsedDom.querySelector('style');
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const styleText = style.textContent;
|
2018-08-15 13:58:42 +02:00
|
|
|
|
style.parentNode.removeChild(style);
|
|
|
|
|
style = frameDoc.createElement('style');
|
|
|
|
|
style.textContent = styleText;
|
|
|
|
|
parsedDom.head.appendChild(style);
|
|
|
|
|
|
2015-02-11 17:49:50 +01:00
|
|
|
|
frameDoc.replaceChild(
|
|
|
|
|
frameDoc.adoptNode(parsedDom.documentElement),
|
|
|
|
|
frameDoc.documentElement
|
2015-02-08 19:34:28 +01:00
|
|
|
|
);
|
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
pickerBody = frameDoc.body;
|
|
|
|
|
pickerBody.setAttribute('lang', navigator.language);
|
2017-05-27 17:51:24 +02:00
|
|
|
|
pickerBody.classList.toggle('zap', details.zap === true);
|
2015-02-09 14:03:29 +01:00
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
dialog = pickerBody.querySelector('aside');
|
2015-02-09 14:03:29 +01:00
|
|
|
|
dialog.addEventListener('click', onDialogClicked);
|
|
|
|
|
|
|
|
|
|
taCandidate = dialog.querySelector('textarea');
|
2015-02-08 19:34:28 +01:00
|
|
|
|
taCandidate.addEventListener('input', onCandidateChanged);
|
|
|
|
|
|
2019-11-05 18:03:48 +01:00
|
|
|
|
dialog.querySelector('#toolbar').addEventListener('mousedown', onStartMoving);
|
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
svgRoot = pickerBody.querySelector('svg');
|
2015-02-09 14:03:29 +01:00
|
|
|
|
svgOcean = svgRoot.firstChild;
|
|
|
|
|
svgIslands = svgRoot.lastChild;
|
2015-03-09 15:11:22 +01:00
|
|
|
|
svgRoot.addEventListener('click', onSvgClicked);
|
2017-05-29 16:38:22 +02:00
|
|
|
|
svgRoot.addEventListener('touchstart', onSvgTouchStartStop);
|
|
|
|
|
svgRoot.addEventListener('touchend', onSvgTouchStartStop);
|
2015-02-09 14:03:29 +01:00
|
|
|
|
svgListening(true);
|
|
|
|
|
|
|
|
|
|
window.addEventListener('scroll', onScrolled, true);
|
|
|
|
|
pickerRoot.contentWindow.addEventListener('keydown', onKeyPressed, true);
|
|
|
|
|
pickerRoot.contentWindow.focus();
|
|
|
|
|
|
2015-03-20 16:39:20 +01:00
|
|
|
|
// Restore net filter union data if it originate from the same URL.
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const eprom = details.eprom || null;
|
2015-08-18 17:44:24 +02:00
|
|
|
|
if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) {
|
2015-03-20 16:39:20 +01:00
|
|
|
|
lastNetFilterHostname = eprom.lastNetFilterHostname || '';
|
|
|
|
|
lastNetFilterUnion = eprom.lastNetFilterUnion || '';
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-08 19:34:28 +01:00
|
|
|
|
// Auto-select a specific target, if any, and if possible
|
|
|
|
|
|
2015-02-09 14:03:29 +01:00
|
|
|
|
highlightElements([], true);
|
|
|
|
|
|
2015-02-08 19:34:28 +01:00
|
|
|
|
// Try using mouse position
|
2019-09-18 18:17:45 +02:00
|
|
|
|
if (
|
|
|
|
|
details.mouse &&
|
|
|
|
|
typeof vAPI.mouseClick.x === 'number' &&
|
|
|
|
|
vAPI.mouseClick.x > 0
|
|
|
|
|
) {
|
|
|
|
|
if ( filtersFrom(vAPI.mouseClick.x, vAPI.mouseClick.y) !== 0 ) {
|
2015-02-08 19:34:28 +01:00
|
|
|
|
showDialog();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No mouse position available, use suggested target
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const target = details.target || '';
|
|
|
|
|
const pos = target.indexOf('\t');
|
2018-08-15 13:58:42 +02:00
|
|
|
|
if ( pos === -1 ) { return; }
|
|
|
|
|
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const srcAttrMap = {
|
2015-02-08 19:34:28 +01:00
|
|
|
|
'a': 'href',
|
2016-04-15 18:27:53 +02:00
|
|
|
|
'audio': 'src',
|
2015-02-10 16:50:47 +01:00
|
|
|
|
'embed': 'src',
|
2016-04-15 18:27:53 +02:00
|
|
|
|
'iframe': 'src',
|
|
|
|
|
'img': 'src',
|
2015-02-08 19:34:28 +01:00
|
|
|
|
'video': 'src',
|
|
|
|
|
};
|
2018-11-06 19:11:03 +01:00
|
|
|
|
const tagName = target.slice(0, pos);
|
|
|
|
|
const url = target.slice(pos + 1);
|
|
|
|
|
const attr = srcAttrMap[tagName];
|
|
|
|
|
if ( attr === undefined ) { return; }
|
|
|
|
|
const elems = document.getElementsByTagName(tagName);
|
2018-11-06 19:20:44 +01:00
|
|
|
|
for ( const elem of elems ) {
|
2018-11-06 19:11:03 +01:00
|
|
|
|
if ( elem === pickerRoot ) { continue; }
|
|
|
|
|
const src = elem[attr];
|
|
|
|
|
if ( typeof src !== 'string' ) { continue; }
|
|
|
|
|
if (
|
|
|
|
|
(src !== url) &&
|
|
|
|
|
(src !== '' || url !== 'about:blank')
|
|
|
|
|
) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-05-25 00:50:09 +02:00
|
|
|
|
elem.scrollIntoView({
|
|
|
|
|
behavior: 'smooth',
|
|
|
|
|
block: 'start'
|
|
|
|
|
});
|
2016-04-15 18:27:53 +02:00
|
|
|
|
filtersFrom(elem);
|
2015-02-08 19:34:28 +01:00
|
|
|
|
showDialog({ modifier: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-06-17 19:49:43 +02:00
|
|
|
|
|
|
|
|
|
// A target was specified, but it wasn't found: abort.
|
|
|
|
|
stopPicker();
|
2015-02-08 19:34:28 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-09-17 21:15:01 +02:00
|
|
|
|
const bootstrapPicker = async function() {
|
2017-10-04 17:14:24 +02:00
|
|
|
|
vAPI.shutdown.add(stopPicker);
|
2019-09-17 21:15:01 +02:00
|
|
|
|
const details = await vAPI.messaging.send('elementPicker', {
|
|
|
|
|
what: 'elementPickerArguments',
|
|
|
|
|
});
|
|
|
|
|
startPicker(details);
|
2017-10-04 17:14:24 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2015-02-09 14:03:29 +01:00
|
|
|
|
pickerRoot = document.createElement('iframe');
|
|
|
|
|
pickerRoot.id = vAPI.sessionId;
|
2017-10-21 19:43:46 +02:00
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const pickerCSSStyle = [
|
2017-10-22 14:59:29 +02:00
|
|
|
|
'background: transparent',
|
|
|
|
|
'border: 0',
|
|
|
|
|
'border-radius: 0',
|
|
|
|
|
'box-shadow: none',
|
|
|
|
|
'display: block',
|
|
|
|
|
'height: 100%',
|
|
|
|
|
'left: 0',
|
|
|
|
|
'margin: 0',
|
|
|
|
|
'max-height: none',
|
|
|
|
|
'max-width: none',
|
|
|
|
|
'opacity: 1',
|
|
|
|
|
'outline: 0',
|
|
|
|
|
'padding: 0',
|
|
|
|
|
'position: fixed',
|
|
|
|
|
'top: 0',
|
|
|
|
|
'visibility: visible',
|
|
|
|
|
'width: 100%',
|
|
|
|
|
'z-index: 2147483647',
|
|
|
|
|
''
|
|
|
|
|
].join(' !important;');
|
|
|
|
|
pickerRoot.style.cssText = pickerCSSStyle;
|
|
|
|
|
|
2019-01-23 23:11:07 +01:00
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/393
|
|
|
|
|
// This needs to be injected as an inline style, *never* as a user styles,
|
|
|
|
|
// hence why it's not added above as part of the pickerCSSStyle
|
|
|
|
|
// properties.
|
|
|
|
|
pickerRoot.style.setProperty('pointer-events', 'auto', 'important');
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const pickerCSS1 = [
|
2019-01-15 22:34:57 +01:00
|
|
|
|
`#${pickerRoot.id} {`,
|
2017-10-22 14:59:29 +02:00
|
|
|
|
pickerCSSStyle,
|
|
|
|
|
'}'
|
|
|
|
|
].join('\n');
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const pickerCSS2 = [
|
2019-01-15 22:34:57 +01:00
|
|
|
|
`[${pickerRoot.id}-clickblind] {`,
|
2017-10-22 14:59:29 +02:00
|
|
|
|
'pointer-events: none !important;',
|
|
|
|
|
'}'
|
|
|
|
|
].join('\n');
|
2015-02-09 14:03:29 +01:00
|
|
|
|
|
2016-04-03 14:34:28 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1529
|
2017-10-21 19:43:46 +02:00
|
|
|
|
// In addition to inline styles, harden the element picker styles by using
|
|
|
|
|
// dedicated CSS rules.
|
2017-10-22 14:59:29 +02:00
|
|
|
|
vAPI.userStylesheet.add(pickerCSS1);
|
|
|
|
|
vAPI.userStylesheet.add(pickerCSS2);
|
|
|
|
|
vAPI.userStylesheet.apply();
|
2016-04-03 14:34:28 +02:00
|
|
|
|
|
2016-10-08 21:14:24 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2060
|
2017-10-21 19:43:46 +02:00
|
|
|
|
vAPI.domFilterer.excludeNode(pickerRoot);
|
2016-10-08 21:14:24 +02:00
|
|
|
|
|
2019-09-17 21:15:01 +02:00
|
|
|
|
pickerRoot.addEventListener(
|
|
|
|
|
'load',
|
|
|
|
|
( ) => { bootstrapPicker(); },
|
|
|
|
|
{ once: true }
|
|
|
|
|
);
|
2015-02-09 14:03:29 +01:00
|
|
|
|
document.documentElement.appendChild(pickerRoot);
|
2015-01-18 08:12:39 +01:00
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
})();
|
2018-05-03 15:55:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
|
|
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;
|