2014-07-13 00:32:44 +00:00
|
|
|
/*******************************************************************************
|
|
|
|
|
|
2023-12-04 17:10:34 +00:00
|
|
|
uBlock Origin - a comprehensive, efficient content blocker
|
2018-08-15 11:58:42 +00:00
|
|
|
Copyright (C) 2014-present Raymond Hill
|
2014-07-13 00:32:44 +00: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
|
|
|
|
|
*/
|
|
|
|
|
|
2020-09-01 16:32:12 +00:00
|
|
|
(async ( ) => {
|
2014-07-13 00:32:44 +00:00
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-12-20 16:23:25 +00:00
|
|
|
if ( typeof vAPI !== 'object' ) { return; }
|
2024-04-10 14:28:11 +00:00
|
|
|
if ( vAPI === null ) { return; }
|
2021-07-31 11:15:19 +00:00
|
|
|
|
2023-12-20 16:23:25 +00:00
|
|
|
if ( vAPI.pickerFrame ) { return; }
|
|
|
|
|
vAPI.pickerFrame = true;
|
2017-10-21 17:43:46 +00:00
|
|
|
|
2023-12-20 16:23:25 +00:00
|
|
|
const pickerUniqueId = vAPI.randomToken();
|
2014-09-28 16:05:46 +00:00
|
|
|
|
2023-08-07 17:58:15 +00:00
|
|
|
const reCosmeticAnchor = /^#(\$|\?|\$\?)?#/;
|
2020-11-29 16:31:20 +00:00
|
|
|
|
2019-11-05 17:03:48 +00:00
|
|
|
const netFilterCandidates = [];
|
|
|
|
|
const cosmeticFilterCandidates = [];
|
2014-07-13 00:32:44 +00:00
|
|
|
|
2019-11-05 17:03:48 +00:00
|
|
|
let targetElements = [];
|
|
|
|
|
let candidateElements = [];
|
|
|
|
|
let bestCandidateFilter = null;
|
2014-07-13 00:32:44 +00:00
|
|
|
|
2019-11-05 17:03:48 +00:00
|
|
|
const lastNetFilterSession = window.location.host + window.location.pathname;
|
|
|
|
|
let lastNetFilterHostname = '';
|
|
|
|
|
let lastNetFilterUnion = '';
|
2015-03-20 15:39:20 +00:00
|
|
|
|
2020-09-07 12:28:01 +00:00
|
|
|
const hideBackgroundStyle = 'background-image:none!important;';
|
|
|
|
|
|
2014-07-13 00:32:44 +00:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
const safeQuerySelectorAll = function(node, selector) {
|
2015-02-16 16:21:25 +00:00
|
|
|
if ( node !== null ) {
|
|
|
|
|
try {
|
|
|
|
|
return node.querySelectorAll(selector);
|
2025-01-09 14:33:22 +00:00
|
|
|
} catch {
|
2015-02-16 16:21:25 +00:00
|
|
|
}
|
2015-02-16 16:14:37 +00:00
|
|
|
}
|
2015-02-16 16:21:25 +00:00
|
|
|
return [];
|
2015-02-16 16:14:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
const getElementBoundingClientRect = function(elem) {
|
|
|
|
|
let rect = typeof elem.getBoundingClientRect === 'function'
|
|
|
|
|
? elem.getBoundingClientRect()
|
|
|
|
|
: { height: 0, left: 0, top: 0, width: 0 };
|
2015-12-07 16:09:39 +00:00
|
|
|
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1024
|
|
|
|
|
// Try not returning an empty bounding rect.
|
2015-12-07 16:18:56 +00:00
|
|
|
if ( rect.width !== 0 && rect.height !== 0 ) {
|
|
|
|
|
return rect;
|
2015-12-07 16:09:39 +00:00
|
|
|
}
|
2022-01-07 13:54:23 +00:00
|
|
|
if ( elem.shadowRoot instanceof DocumentFragment ) {
|
|
|
|
|
return getElementBoundingClientRect(elem.shadowRoot);
|
|
|
|
|
}
|
2015-12-07 16:09:39 +00:00
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
let left = rect.left,
|
2022-01-07 13:54:23 +00:00
|
|
|
right = left + rect.width,
|
2015-12-07 16:18:56 +00:00
|
|
|
top = rect.top,
|
2022-01-07 13:54:23 +00:00
|
|
|
bottom = top + rect.height;
|
2015-12-07 16:09:39 +00:00
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
for ( const child of elem.children ) {
|
|
|
|
|
rect = getElementBoundingClientRect(child);
|
2022-01-07 13:54:23 +00:00
|
|
|
if ( rect.width === 0 || rect.height === 0 ) { continue; }
|
2015-12-07 16:18:56 +00: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 16:09:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
2022-01-07 13:54:23 +00:00
|
|
|
bottom,
|
2015-12-07 16:09:39 +00:00
|
|
|
height: bottom - top,
|
2020-09-03 14:27:35 +00:00
|
|
|
left,
|
2022-01-07 13:54:23 +00:00
|
|
|
right,
|
2020-09-03 14:27:35 +00:00
|
|
|
top,
|
2015-12-07 16:09:39 +00:00
|
|
|
width: right - left
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
const highlightElements = function(elems, force) {
|
2014-07-14 06:14:13 +00:00
|
|
|
// To make mouse move handler more efficient
|
2020-09-03 14:27:35 +00:00
|
|
|
if (
|
|
|
|
|
(force !== true) &&
|
|
|
|
|
(elems.length === targetElements.length) &&
|
|
|
|
|
(elems.length === 0 || elems[0] === targetElements[0])
|
|
|
|
|
) {
|
|
|
|
|
return;
|
2014-07-13 00:32:44 +00:00
|
|
|
}
|
2020-09-03 14:27:35 +00:00
|
|
|
targetElements = [];
|
|
|
|
|
|
|
|
|
|
const ow = self.innerWidth;
|
|
|
|
|
const oh = self.innerHeight;
|
2019-06-29 15:06:03 +00:00
|
|
|
const islands = [];
|
2014-11-07 11:59:01 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
for ( const elem of elems ) {
|
2023-12-20 16:23:25 +00:00
|
|
|
if ( elem === pickerFrame ) { continue; }
|
2020-09-03 14:27:35 +00:00
|
|
|
targetElements.push(elem);
|
2019-06-29 15:06:03 +00:00
|
|
|
const rect = getElementBoundingClientRect(elem);
|
2020-09-03 14:27:35 +00:00
|
|
|
// Ignore offscreen areas
|
|
|
|
|
if (
|
|
|
|
|
rect.left > ow || rect.top > oh ||
|
|
|
|
|
rect.left + rect.width < 0 || rect.top + rect.height < 0
|
|
|
|
|
) {
|
2015-02-09 13:03:29 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-03 14:27:35 +00:00
|
|
|
islands.push(
|
|
|
|
|
`M${rect.left} ${rect.top}h${rect.width}v${rect.height}h-${rect.width}z`
|
|
|
|
|
);
|
2014-07-13 00:32:44 +00:00
|
|
|
}
|
2020-09-03 14:27:35 +00:00
|
|
|
|
2023-12-03 21:21:32 +00:00
|
|
|
pickerFramePort.postMessage({
|
2020-09-03 14:27:35 +00:00
|
|
|
what: 'svgPaths',
|
|
|
|
|
ocean: `M0 0h${ow}v${oh}h-${ow}z`,
|
|
|
|
|
islands: islands.join(''),
|
|
|
|
|
});
|
2014-07-13 00:32:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2018-12-20 22:29:39 +00: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].split('').join('\n'),
|
|
|
|
|
merged.split('').join('\n')
|
|
|
|
|
);
|
|
|
|
|
const result = [];
|
|
|
|
|
for ( const diff of diffs ) {
|
|
|
|
|
if ( diff[0] !== 0 ) {
|
|
|
|
|
result.push('*');
|
|
|
|
|
} else {
|
2019-06-25 21:09:04 +00:00
|
|
|
result.push(diff[1].replace(/\n+/g, ''));
|
2018-12-20 22:29:39 +00:00
|
|
|
}
|
2020-03-15 12:45:17 +00:00
|
|
|
merged = result.join('');
|
2018-12-20 22:29:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
2020-03-15 12:45:17 +00:00
|
|
|
// Keep usage of wildcards to a sane level, too many of them can cause
|
|
|
|
|
// high overhead filters
|
|
|
|
|
merged = merged.replace(/^\*+$/, '')
|
|
|
|
|
.replace(/\*{2,}/g, '*')
|
|
|
|
|
.replace(/([^*]{1,3}\*)(?:[^*]{1,3}\*)+/g, '$1');
|
2021-02-22 12:57:23 +00:00
|
|
|
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/1494
|
|
|
|
|
let pos = merged.indexOf('/');
|
|
|
|
|
if ( pos === -1 ) { pos = merged.length; }
|
|
|
|
|
return merged.slice(0, pos).includes('*') ? urls[0] : merged;
|
2018-12-20 22:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2020-03-15 12:45:17 +00:00
|
|
|
// Remove fragment part from a URL.
|
|
|
|
|
|
|
|
|
|
const trimFragmentFromURL = function(url) {
|
|
|
|
|
const pos = url.indexOf('#');
|
|
|
|
|
return pos !== -1 ? url.slice(0, pos) : url;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2016-08-15 11:52:50 +00:00
|
|
|
// https://github.com/gorhill/uBlock/issues/1897
|
|
|
|
|
// Ignore `data:` URI, they can't be handled by an HTTP observer.
|
|
|
|
|
|
2018-12-20 22:29:39 +00: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] : '';
|
2020-03-15 12:45:17 +00:00
|
|
|
return url.lastIndexOf('data:', 0) === -1
|
|
|
|
|
? trimFragmentFromURL(url.slice(0, 1024))
|
|
|
|
|
: '';
|
2016-04-15 16:27:53 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2016-06-16 13:15:49 +00:00
|
|
|
// https://github.com/gorhill/uBlock/issues/1725#issuecomment-226479197
|
2018-12-20 22:29:39 +00:00
|
|
|
// Limit returned string to 1024 characters.
|
|
|
|
|
// Also, return only URLs which will be seen by an HTTP observer.
|
2022-09-11 16:20:01 +00:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/2260
|
|
|
|
|
// Maybe get to the actual URL indirectly.
|
2020-03-15 12:45:17 +00:00
|
|
|
const resourceURLsFromElement = function(elem) {
|
|
|
|
|
const urls = [];
|
2018-12-20 22:29:39 +00:00
|
|
|
const tagName = elem.localName;
|
|
|
|
|
const prop = netFilter1stSources[tagName];
|
2020-03-15 12:45:17 +00:00
|
|
|
if ( prop === undefined ) {
|
|
|
|
|
const url = backgroundImageURLFromElement(elem);
|
|
|
|
|
if ( url !== '' ) { urls.push(url); }
|
|
|
|
|
return urls;
|
|
|
|
|
}
|
2022-09-11 16:20:01 +00:00
|
|
|
let s = elem[prop];
|
|
|
|
|
if ( s instanceof SVGAnimatedString ) {
|
|
|
|
|
s = s.baseVal;
|
|
|
|
|
}
|
2022-03-28 16:11:18 +00:00
|
|
|
if ( typeof s === 'string' && /^https?:\/\//.test(s) ) {
|
|
|
|
|
urls.push(trimFragmentFromURL(s.slice(0, 1024)));
|
2020-03-15 12:45:17 +00:00
|
|
|
}
|
2020-07-05 12:44:14 +00:00
|
|
|
resourceURLsFromSrcset(elem, urls);
|
2022-03-28 16:11:18 +00:00
|
|
|
resourceURLsFromPicture(elem, urls);
|
2020-07-05 12:44:14 +00:00
|
|
|
return urls;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// https://html.spec.whatwg.org/multipage/images.html#parsing-a-srcset-attribute
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/1071
|
|
|
|
|
const resourceURLsFromSrcset = function(elem, out) {
|
|
|
|
|
let srcset = elem.srcset;
|
2020-07-13 15:46:38 +00:00
|
|
|
if ( typeof srcset !== 'string' || srcset === '' ) { return; }
|
2020-07-05 12:44:14 +00:00
|
|
|
for(;;) {
|
|
|
|
|
// trim whitespace
|
|
|
|
|
srcset = srcset.trim();
|
|
|
|
|
if ( srcset.length === 0 ) { break; }
|
|
|
|
|
// abort in case of leading comma
|
|
|
|
|
if ( /^,/.test(srcset) ) { break; }
|
|
|
|
|
// collect and consume all non-whitespace characters
|
|
|
|
|
let match = /^\S+/.exec(srcset);
|
|
|
|
|
if ( match === null ) { break; }
|
|
|
|
|
srcset = srcset.slice(match.index + match[0].length);
|
|
|
|
|
let url = match[0];
|
|
|
|
|
// consume descriptor, if any
|
|
|
|
|
if ( /,$/.test(url) ) {
|
|
|
|
|
url = url.replace(/,$/, '');
|
|
|
|
|
if ( /,$/.test(url) ) { break; }
|
|
|
|
|
} else {
|
|
|
|
|
match = /^[^,]*(?:\(.+?\))?[^,]*(?:,|$)/.exec(srcset);
|
|
|
|
|
if ( match === null ) { break; }
|
|
|
|
|
srcset = srcset.slice(match.index + match[0].length);
|
2016-04-15 16:27:53 +00:00
|
|
|
}
|
2020-07-05 12:44:14 +00:00
|
|
|
const parsedURL = new URL(url, document.baseURI);
|
|
|
|
|
if ( parsedURL.pathname.length === 0 ) { continue; }
|
|
|
|
|
out.push(trimFragmentFromURL(parsedURL.href));
|
2016-04-15 16:27:53 +00:00
|
|
|
}
|
2014-07-13 00:32:44 +00:00
|
|
|
};
|
|
|
|
|
|
2022-03-28 16:11:18 +00:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/2069#issuecomment-1080600661
|
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture
|
|
|
|
|
const resourceURLsFromPicture = function(elem, out) {
|
|
|
|
|
if ( elem.localName === 'source' ) { return; }
|
|
|
|
|
const picture = elem.parentElement;
|
|
|
|
|
if ( picture === null || picture.localName !== 'picture' ) { return; }
|
|
|
|
|
const sources = picture.querySelectorAll(':scope > source');
|
|
|
|
|
for ( const source of sources ) {
|
|
|
|
|
const urls = resourceURLsFromElement(source);
|
|
|
|
|
if ( urls.length === 0 ) { continue; }
|
|
|
|
|
out.push(...urls);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2014-07-13 00:32:44 +00:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2020-03-15 12:45:17 +00:00
|
|
|
const netFilterFromUnion = function(patternIn, out) {
|
2018-12-20 22:29:39 +00:00
|
|
|
// Reset reference filter when dealing with unrelated URLs
|
2020-03-15 12:45:17 +00:00
|
|
|
const currentHostname = self.location.hostname;
|
2018-12-20 22:29:39 +00:00
|
|
|
if (
|
|
|
|
|
lastNetFilterUnion === '' ||
|
2020-03-15 12:45:17 +00:00
|
|
|
currentHostname === '' ||
|
|
|
|
|
currentHostname !== lastNetFilterHostname
|
2018-12-20 22:29:39 +00:00
|
|
|
) {
|
2020-03-15 12:45:17 +00:00
|
|
|
lastNetFilterHostname = currentHostname;
|
|
|
|
|
lastNetFilterUnion = patternIn;
|
2019-09-17 19:15:01 +00:00
|
|
|
vAPI.messaging.send('elementPicker', {
|
|
|
|
|
what: 'elementPickerEprom',
|
2020-03-15 12:45:17 +00:00
|
|
|
lastNetFilterSession,
|
|
|
|
|
lastNetFilterHostname,
|
|
|
|
|
lastNetFilterUnion,
|
2019-09-17 19:15:01 +00:00
|
|
|
});
|
2018-12-20 22:29:39 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Related URLs
|
2020-03-15 12:45:17 +00:00
|
|
|
lastNetFilterHostname = currentHostname;
|
|
|
|
|
let patternOut = mergeStrings([ patternIn, lastNetFilterUnion ]);
|
|
|
|
|
if ( patternOut !== '/*' && patternOut !== patternIn ) {
|
|
|
|
|
const filter = `||${patternOut}`;
|
2018-12-20 22:29:39 +00:00
|
|
|
if ( out.indexOf(filter) === -1 ) {
|
|
|
|
|
out.push(filter);
|
|
|
|
|
}
|
2020-03-15 12:45:17 +00:00
|
|
|
lastNetFilterUnion = patternOut;
|
2018-12-20 22:29:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remember across element picker sessions
|
2019-09-17 19:15:01 +00:00
|
|
|
vAPI.messaging.send('elementPicker', {
|
|
|
|
|
what: 'elementPickerEprom',
|
2020-03-15 12:45:17 +00:00
|
|
|
lastNetFilterSession,
|
|
|
|
|
lastNetFilterHostname,
|
|
|
|
|
lastNetFilterUnion,
|
2019-09-17 19:15:01 +00:00
|
|
|
});
|
2018-12-20 22:29:39 +00:00
|
|
|
};
|
2015-03-19 14:13:51 +00:00
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-07-13 00:32:44 +00:00
|
|
|
// Extract the best possible net filter, i.e. as specific as possible.
|
|
|
|
|
|
2018-12-20 22:29:39 +00:00
|
|
|
const netFilterFromElement = function(elem) {
|
2018-11-06 18:11:03 +00:00
|
|
|
if ( elem === null ) { return 0; }
|
|
|
|
|
if ( elem.nodeType !== 1 ) { return 0; }
|
2020-03-15 12:45:17 +00:00
|
|
|
const urls = resourceURLsFromElement(elem);
|
|
|
|
|
if ( urls.length === 0 ) { return 0; }
|
2016-04-15 16:27:53 +00:00
|
|
|
|
2016-04-16 15:20:01 +00:00
|
|
|
if ( candidateElements.indexOf(elem) === -1 ) {
|
|
|
|
|
candidateElements.push(elem);
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-06 18:11:03 +00:00
|
|
|
const candidates = netFilterCandidates;
|
|
|
|
|
const len = candidates.length;
|
2016-04-16 15:20:01 +00:00
|
|
|
|
2020-03-15 12:45:17 +00:00
|
|
|
for ( let i = 0; i < urls.length; i++ ) {
|
|
|
|
|
urls[i] = urls[i].replace(/^https?:\/\//, '');
|
2014-09-28 16:05:46 +00:00
|
|
|
}
|
2020-03-15 12:45:17 +00:00
|
|
|
const pattern = mergeStrings(urls);
|
2015-03-19 14:13:51 +00:00
|
|
|
|
|
|
|
|
|
2022-01-13 14:24:04 +00:00
|
|
|
if ( bestCandidateFilter === null && elem.matches('html,body') === false ) {
|
2016-04-16 15:20:01 +00:00
|
|
|
bestCandidateFilter = {
|
2016-04-17 14:15:01 +00:00
|
|
|
type: 'net',
|
2016-04-16 15:20:01 +00:00
|
|
|
filters: candidates,
|
|
|
|
|
slot: candidates.length
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-15 12:45:17 +00:00
|
|
|
candidates.push(`||${pattern}`);
|
2015-03-19 14:13:51 +00:00
|
|
|
|
2014-09-28 16:05:46 +00:00
|
|
|
// Suggest a less narrow filter if possible
|
2020-03-15 12:45:17 +00:00
|
|
|
const pos = pattern.indexOf('?');
|
2014-09-28 16:05:46 +00:00
|
|
|
if ( pos !== -1 ) {
|
2020-03-15 12:45:17 +00:00
|
|
|
candidates.push(`||${pattern.slice(0, pos)}`);
|
2014-09-28 16:05:46 +00:00
|
|
|
}
|
2015-03-19 14:13:51 +00:00
|
|
|
|
|
|
|
|
// Suggest a filter which is a result of combining more than one URL.
|
2020-03-15 12:45:17 +00:00
|
|
|
netFilterFromUnion(pattern, candidates);
|
2016-04-16 15:20:01 +00:00
|
|
|
|
|
|
|
|
return candidates.length - len;
|
2014-07-13 00:32:44 +00:00
|
|
|
};
|
|
|
|
|
|
2018-12-20 22:29:39 +00:00
|
|
|
const netFilter1stSources = {
|
2016-04-15 16:27:53 +00:00
|
|
|
'audio': 'src',
|
2015-02-10 15:50:47 +00:00
|
|
|
'embed': 'src',
|
2015-02-03 23:43:51 +00:00
|
|
|
'iframe': 'src',
|
|
|
|
|
'img': 'src',
|
2022-09-11 16:20:01 +00:00
|
|
|
'image': 'href',
|
2016-04-15 16:27:53 +00:00
|
|
|
'object': 'data',
|
2022-03-28 16:11:18 +00:00
|
|
|
'source': 'src',
|
2016-04-15 16:27:53 +00:00
|
|
|
'video': 'src'
|
2015-02-03 23:43:51 +00:00
|
|
|
};
|
|
|
|
|
|
2018-12-20 22:29:39 +00:00
|
|
|
const filterTypes = {
|
2016-04-15 16:27:53 +00:00
|
|
|
'audio': 'media',
|
|
|
|
|
'embed': 'object',
|
|
|
|
|
'iframe': 'subdocument',
|
|
|
|
|
'img': 'image',
|
|
|
|
|
'object': 'object',
|
|
|
|
|
'video': 'media',
|
|
|
|
|
};
|
|
|
|
|
|
2014-07-13 00:32:44 +00:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
// Extract the best possible cosmetic filter, i.e. as specific as possible.
|
|
|
|
|
|
2016-06-16 14:55:49 +00:00
|
|
|
// https://github.com/gorhill/uBlock/issues/1725
|
2018-04-29 13:07:12 +00:00
|
|
|
// Also take into account the `src` attribute for `img` elements -- and limit
|
|
|
|
|
// the value to the 1024 first characters.
|
2016-06-16 14:55:49 +00:00
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
const cosmeticFilterFromElement = function(elem) {
|
2018-04-29 13:07:12 +00:00
|
|
|
if ( elem === null ) { return 0; }
|
|
|
|
|
if ( elem.nodeType !== 1 ) { return 0; }
|
2021-07-12 15:55:58 +00:00
|
|
|
if ( noCosmeticFiltering ) { return 0; }
|
2016-04-16 15:20:01 +00:00
|
|
|
|
|
|
|
|
if ( candidateElements.indexOf(elem) === -1 ) {
|
|
|
|
|
candidateElements.push(elem);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 13:07:12 +00:00
|
|
|
let selector = '';
|
2014-07-13 00:32:44 +00:00
|
|
|
|
|
|
|
|
// Id
|
2018-04-29 13:07:12 +00:00
|
|
|
let v = typeof elem.id === 'string' && CSS.escape(elem.id);
|
2014-07-13 00:32:44 +00:00
|
|
|
if ( v ) {
|
2016-04-17 14:15:01 +00:00
|
|
|
selector = '#' + v;
|
2014-07-13 00:32:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Class(es)
|
2017-02-13 13:33:10 +00:00
|
|
|
v = elem.classList;
|
|
|
|
|
if ( v ) {
|
2018-04-29 13:07:12 +00:00
|
|
|
let i = v.length || 0;
|
2017-02-13 13:33:10 +00:00
|
|
|
while ( i-- ) {
|
|
|
|
|
selector += '.' + CSS.escape(v.item(i));
|
2014-07-13 00:32:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-20 22:38:28 +00:00
|
|
|
// Tag name
|
2021-03-04 11:19:44 +00:00
|
|
|
const tagName = CSS.escape(elem.localName);
|
2018-04-29 13:07:12 +00:00
|
|
|
|
|
|
|
|
// Use attributes if still no selector found.
|
2016-08-15 17:10:32 +00:00
|
|
|
// https://github.com/gorhill/uBlock/issues/1901
|
2018-04-29 13:07:12 +00:00
|
|
|
// Trim attribute value, this may help in case of malformed HTML.
|
2022-01-11 12:20:03 +00:00
|
|
|
//
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/1923
|
|
|
|
|
// Escape unescaped `"` in attribute values
|
2016-04-17 14:15:01 +00:00
|
|
|
if ( selector === '' ) {
|
2018-04-29 13:07:12 +00:00
|
|
|
let attributes = [], attr;
|
2016-04-17 14:15:01 +00:00
|
|
|
switch ( tagName ) {
|
|
|
|
|
case 'a':
|
|
|
|
|
v = elem.getAttribute('href');
|
|
|
|
|
if ( v ) {
|
2016-08-15 17:10:32 +00:00
|
|
|
v = v.trim().replace(/\?.*$/, '');
|
2016-04-17 14:15:01 +00:00
|
|
|
if ( v.length ) {
|
|
|
|
|
attributes.push({ k: 'href', v: v });
|
|
|
|
|
}
|
2014-10-13 13:47:18 +00:00
|
|
|
}
|
2016-04-17 14:15:01 +00:00
|
|
|
break;
|
2016-08-21 13:49:11 +00:00
|
|
|
case 'iframe':
|
2016-04-17 14:15:01 +00:00
|
|
|
case 'img':
|
2016-06-16 14:55:49 +00:00
|
|
|
v = elem.getAttribute('src');
|
|
|
|
|
if ( v && v.length !== 0 ) {
|
2018-11-06 18:11:03 +00:00
|
|
|
v = v.trim();
|
|
|
|
|
if ( v.startsWith('data:') ) {
|
|
|
|
|
let pos = v.indexOf(',');
|
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
|
v = v.slice(0, pos + 1);
|
|
|
|
|
}
|
2018-11-06 19:22:09 +00:00
|
|
|
} else if ( v.startsWith('blob:') ) {
|
|
|
|
|
v = new URL(v.slice(5));
|
|
|
|
|
v.pathname = '';
|
|
|
|
|
v = 'blob:' + v.href;
|
2018-11-06 18:11:03 +00:00
|
|
|
}
|
|
|
|
|
attributes.push({ k: 'src', v: v.slice(0, 256) });
|
2016-06-16 14:55:49 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2016-04-17 14:15:01 +00:00
|
|
|
v = elem.getAttribute('alt');
|
|
|
|
|
if ( v && v.length !== 0 ) {
|
|
|
|
|
attributes.push({ k: 'alt', v: v });
|
2016-06-16 14:55:49 +00:00
|
|
|
break;
|
2016-04-17 14:15:01 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2014-07-13 00:32:44 +00:00
|
|
|
}
|
2016-04-17 14:15:01 +00:00
|
|
|
while ( (attr = attributes.pop()) ) {
|
2018-04-29 13:07:12 +00:00
|
|
|
if ( attr.v.length === 0 ) { continue; }
|
2022-01-11 12:20:03 +00:00
|
|
|
const w = attr.v.replace(/([^\\])"/g, '$1\\"');
|
2016-04-17 14:15:01 +00:00
|
|
|
v = elem.getAttribute(attr.k);
|
|
|
|
|
if ( attr.v === v ) {
|
2022-01-11 12:20:03 +00:00
|
|
|
selector += `[${attr.k}="${w}"]`;
|
2018-04-29 13:07:12 +00:00
|
|
|
} else if ( v.startsWith(attr.v) ) {
|
2022-01-11 12:20:03 +00:00
|
|
|
selector += `[${attr.k}^="${w}"]`;
|
2016-04-17 14:15:01 +00:00
|
|
|
} else {
|
2022-01-11 12:20:03 +00:00
|
|
|
selector += `[${attr.k}*="${w}"]`;
|
2016-04-17 14:15:01 +00:00
|
|
|
}
|
2014-07-13 00:32:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 13:07:12 +00: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 18:11:03 +00:00
|
|
|
const parentNode = elem.parentNode;
|
2018-04-29 13:07:12 +00:00
|
|
|
if (
|
|
|
|
|
selector === '' ||
|
2020-09-01 16:32:12 +00:00
|
|
|
safeQuerySelectorAll(parentNode, `:scope > ${selector}`).length > 1
|
2018-04-29 13:07:12 +00:00
|
|
|
) {
|
|
|
|
|
selector = tagName + selector;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-07 01:26:05 +00:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/637
|
2018-04-29 13:07:12 +00: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.
|
2020-09-01 16:32:12 +00:00
|
|
|
if ( safeQuerySelectorAll(parentNode, `:scope > ${selector}`).length > 1 ) {
|
2018-04-29 13:07:12 +00:00
|
|
|
let i = 1;
|
2015-01-30 05:49:30 +00:00
|
|
|
while ( elem.previousSibling !== null ) {
|
|
|
|
|
elem = elem.previousSibling;
|
2018-04-29 13:07:12 +00:00
|
|
|
if (
|
|
|
|
|
typeof elem.localName === 'string' &&
|
|
|
|
|
elem.localName === tagName
|
|
|
|
|
) {
|
|
|
|
|
i++;
|
2015-01-30 05:49:30 +00:00
|
|
|
}
|
|
|
|
|
}
|
2020-09-01 16:32:12 +00:00
|
|
|
selector += `:nth-of-type(${i})`;
|
2015-01-30 05:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
2016-04-16 15:20:01 +00:00
|
|
|
if ( bestCandidateFilter === null ) {
|
|
|
|
|
bestCandidateFilter = {
|
2016-04-17 14:15:01 +00:00
|
|
|
type: 'cosmetic',
|
2016-04-16 15:20:01 +00:00
|
|
|
filters: cosmeticFilterCandidates,
|
|
|
|
|
slot: cosmeticFilterCandidates.length
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-01 16:32:12 +00:00
|
|
|
cosmeticFilterCandidates.push(`##${selector}`);
|
2016-04-16 15:20:01 +00:00
|
|
|
|
|
|
|
|
return 1;
|
2014-07-13 00:32:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
const filtersFrom = function(x, y) {
|
2016-04-16 15:20:01 +00:00
|
|
|
bestCandidateFilter = null;
|
2014-09-28 16:05:46 +00:00
|
|
|
netFilterCandidates.length = 0;
|
|
|
|
|
cosmeticFilterCandidates.length = 0;
|
2016-04-16 15:20:01 +00:00
|
|
|
candidateElements.length = 0;
|
2016-04-15 16:27:53 +00:00
|
|
|
|
|
|
|
|
// We need at least one element.
|
2018-04-29 13:07:12 +00:00
|
|
|
let first = null;
|
2016-04-15 16:27:53 +00:00
|
|
|
if ( typeof x === 'number' ) {
|
|
|
|
|
first = elementFromPoint(x, y);
|
|
|
|
|
} else if ( x instanceof HTMLElement ) {
|
|
|
|
|
first = x;
|
|
|
|
|
x = undefined;
|
|
|
|
|
}
|
2016-04-16 15:20:01 +00:00
|
|
|
|
2021-11-04 16:42:48 +00:00
|
|
|
// https://github.com/gorhill/uBlock/issues/1545
|
|
|
|
|
// Network filter candidates from all other elements found at [x,y].
|
|
|
|
|
// https://www.reddit.com/r/uBlockOrigin/comments/qmjk36/
|
|
|
|
|
// Extract network candidates first.
|
|
|
|
|
if ( typeof x === 'number' ) {
|
2023-12-20 16:23:25 +00:00
|
|
|
const magicAttr = `${pickerUniqueId}-clickblind`;
|
|
|
|
|
pickerFrame.setAttribute(magicAttr, '');
|
2022-11-13 16:07:41 +00:00
|
|
|
const elems = document.elementsFromPoint(x, y);
|
2023-12-20 16:23:25 +00:00
|
|
|
pickerFrame.removeAttribute(magicAttr);
|
2021-11-04 16:42:48 +00:00
|
|
|
for ( const elem of elems ) {
|
|
|
|
|
netFilterFromElement(elem);
|
|
|
|
|
}
|
|
|
|
|
} else if ( first !== null ) {
|
|
|
|
|
netFilterFromElement(first);
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-16 15:20:01 +00:00
|
|
|
// Cosmetic filter candidates from ancestors.
|
2020-09-09 13:27:53 +00:00
|
|
|
// https://github.com/gorhill/uBlock/issues/2519
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/17
|
|
|
|
|
// Prepend `body` if full selector is ambiguous.
|
2022-11-13 16:07:41 +00:00
|
|
|
let elem = first;
|
|
|
|
|
while ( elem && elem !== document.body ) {
|
2016-04-16 15:20:01 +00:00
|
|
|
cosmeticFilterFromElement(elem);
|
2022-11-13 16:07:41 +00:00
|
|
|
elem = elem.parentNode;
|
2014-07-13 00:32:44 +00:00
|
|
|
}
|
2015-03-20 22:38:28 +00:00
|
|
|
// The body tag is needed as anchor only when the immediate child
|
2018-04-29 13:07:12 +00:00
|
|
|
// uses `nth-of-type`.
|
|
|
|
|
let i = cosmeticFilterCandidates.length;
|
|
|
|
|
if ( i !== 0 ) {
|
2020-09-10 14:32:53 +00:00
|
|
|
const selector = cosmeticFilterCandidates[i-1].slice(2);
|
|
|
|
|
if ( safeQuerySelectorAll(document.body, selector).length > 1 ) {
|
2018-04-29 13:07:12 +00:00
|
|
|
cosmeticFilterCandidates.push('##body');
|
|
|
|
|
}
|
2015-03-20 22:38:28 +00:00
|
|
|
}
|
2016-04-15 16:27:53 +00:00
|
|
|
|
2022-01-13 16:03:21 +00:00
|
|
|
// https://github.com/gorhill/uBlock/commit/ebaa8a8bb28aef043a68c99965fe6c128a3fe5e4#commitcomment-63818019
|
|
|
|
|
// If still no best candidate, just use whatever is available in network
|
|
|
|
|
// filter candidates -- which may have been previously skipped in favor
|
|
|
|
|
// of cosmetic filters.
|
|
|
|
|
if ( bestCandidateFilter === null && netFilterCandidates.length !== 0 ) {
|
|
|
|
|
bestCandidateFilter = {
|
|
|
|
|
type: 'net',
|
|
|
|
|
filters: netFilterCandidates,
|
|
|
|
|
slot: 0
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-15 16:27:53 +00:00
|
|
|
return netFilterCandidates.length + cosmeticFilterCandidates.length;
|
|
|
|
|
};
|
|
|
|
|
|
2016-10-01 16:34:25 +00:00
|
|
|
/*******************************************************************************
|
2016-04-15 16:27:53 +00:00
|
|
|
|
2020-09-01 16:32:12 +00:00
|
|
|
filterToDOMInterface.queryAll
|
2016-10-01 16:34:25 +00:00
|
|
|
@desc Look-up all the HTML elements matching the filter passed in
|
|
|
|
|
argument.
|
2016-12-25 21:56:39 +00:00
|
|
|
@param string, a cosmetic or network filter.
|
|
|
|
|
@param function, called once all items matching the filter have been
|
|
|
|
|
collected.
|
2016-10-01 16:34:25 +00:00
|
|
|
@return array, or undefined if the filter is invalid.
|
2014-09-28 16:05:46 +00:00
|
|
|
|
2016-10-01 16:34:25 +00: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 17:59:27 +00:00
|
|
|
|
2016-10-01 16:34:25 +00:00
|
|
|
TODO: need to be revised once I implement chained cosmetic operators.
|
|
|
|
|
|
|
|
|
|
*/
|
2019-06-29 15:06:03 +00:00
|
|
|
|
|
|
|
|
const filterToDOMInterface = (( ) => {
|
|
|
|
|
const reHnAnchorPrefix = '^[\\w-]+://(?:[^/?#]+\\.)?';
|
|
|
|
|
const reCaret = '(?:[^%.0-9a-z_-]|$)';
|
2019-08-11 20:32:49 +00:00
|
|
|
const rePseudoElements = /:(?::?after|:?before|:[a-z-]+)$/;
|
2019-06-29 15:06:03 +00:00
|
|
|
|
2024-02-26 21:08:12 +00:00
|
|
|
const matchElemToRegex = (elem, re) => {
|
|
|
|
|
const srcProp = netFilter1stSources[elem.localName];
|
|
|
|
|
let src = elem[srcProp];
|
|
|
|
|
if ( src instanceof SVGAnimatedString ) {
|
|
|
|
|
src = src.baseVal;
|
|
|
|
|
}
|
|
|
|
|
if ( typeof src === 'string' && /^https?:\/\//.test(src) ) {
|
|
|
|
|
if ( re.test(src) ) { return srcProp; }
|
|
|
|
|
}
|
|
|
|
|
src = elem.currentSrc;
|
|
|
|
|
if ( typeof src === 'string' && /^https?:\/\//.test(src) ) {
|
|
|
|
|
if ( re.test(src) ) { return srcProp; }
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2016-10-01 16:34:25 +00:00
|
|
|
// Net filters: we need to lookup manually -- translating into a foolproof
|
|
|
|
|
// CSS selector is just not possible.
|
2019-06-29 15:06:03 +00: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 21:00:42 +00:00
|
|
|
const fromNetworkFilter = function(filter) {
|
|
|
|
|
const out = [];
|
2019-06-29 15:06:03 +00:00
|
|
|
if ( /^[0-9a-z]$/i.test(filter) ) { return out; }
|
2019-02-18 21:00:42 +00:00
|
|
|
let reStr = '';
|
2019-06-29 15:06:03 +00:00
|
|
|
if (
|
|
|
|
|
filter.length > 2 &&
|
|
|
|
|
filter.startsWith('/') &&
|
|
|
|
|
filter.endsWith('/')
|
|
|
|
|
) {
|
2016-10-01 16:34:25 +00:00
|
|
|
reStr = filter.slice(1, -1);
|
2019-06-29 15:06:03 +00:00
|
|
|
} else if ( /^\w[\w.-]*[a-z]$/i.test(filter) ) {
|
|
|
|
|
reStr = reHnAnchorPrefix +
|
|
|
|
|
filter.toLowerCase().replace(/\./g, '\\.') +
|
|
|
|
|
reCaret;
|
|
|
|
|
} else {
|
2019-02-18 21:00:42 +00:00
|
|
|
let rePrefix = '', reSuffix = '';
|
2019-06-29 15:06:03 +00:00
|
|
|
if ( filter.startsWith('||') ) {
|
|
|
|
|
rePrefix = reHnAnchorPrefix;
|
|
|
|
|
filter = filter.slice(2);
|
|
|
|
|
} else if ( filter.startsWith('|') ) {
|
|
|
|
|
rePrefix = '^';
|
|
|
|
|
filter = filter.slice(1);
|
2016-10-01 16:34:25 +00:00
|
|
|
}
|
2019-06-29 15:06:03 +00:00
|
|
|
if ( filter.endsWith('|') ) {
|
2016-10-01 16:34:25 +00:00
|
|
|
reSuffix = '$';
|
|
|
|
|
filter = filter.slice(0, -1);
|
|
|
|
|
}
|
|
|
|
|
reStr = rePrefix +
|
2019-06-29 15:06:03 +00:00
|
|
|
filter.replace(/[.+?${}()|[\]\\]/g, '\\$&')
|
|
|
|
|
.replace(/\*+/g, '.*')
|
|
|
|
|
.replace(/\^/g, reCaret) +
|
2016-10-01 16:34:25 +00:00
|
|
|
reSuffix;
|
|
|
|
|
}
|
2019-02-18 21:00:42 +00:00
|
|
|
let reFilter = null;
|
2014-09-28 16:05:46 +00:00
|
|
|
try {
|
2020-02-24 14:24:54 +00:00
|
|
|
reFilter = new RegExp(reStr, 'i');
|
2025-01-09 14:33:22 +00:00
|
|
|
} catch {
|
2016-10-01 16:34:25 +00:00
|
|
|
return out;
|
2016-04-15 16:27:53 +00:00
|
|
|
}
|
2016-10-01 16:34:25 +00:00
|
|
|
|
|
|
|
|
// Lookup by tag names.
|
2022-09-11 16:20:01 +00:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/2260
|
|
|
|
|
// Maybe get to the actual URL indirectly.
|
2024-02-26 21:08:12 +00:00
|
|
|
//
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/3142
|
|
|
|
|
// Don't try to match against non-network URIs.
|
2019-06-29 15:06:03 +00:00
|
|
|
const elems = document.querySelectorAll(
|
2019-02-18 21:00:42 +00:00
|
|
|
Object.keys(netFilter1stSources).join()
|
|
|
|
|
);
|
|
|
|
|
for ( const elem of elems ) {
|
2024-02-26 21:08:12 +00:00
|
|
|
const srcProp = matchElemToRegex(elem, reFilter);
|
|
|
|
|
if ( srcProp === undefined ) { continue; }
|
|
|
|
|
out.push({
|
|
|
|
|
elem,
|
|
|
|
|
src: srcProp,
|
|
|
|
|
opt: filterTypes[elem.localName],
|
|
|
|
|
style: vAPI.hideStyle,
|
|
|
|
|
});
|
2016-10-01 16:34:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find matching background image in current set of candidate elements.
|
2019-02-18 21:00:42 +00:00
|
|
|
for ( const elem of candidateElements ) {
|
2016-10-01 16:34:25 +00:00
|
|
|
if ( reFilter.test(backgroundImageURLFromElement(elem)) ) {
|
|
|
|
|
out.push({
|
2020-09-07 12:28:01 +00:00
|
|
|
elem,
|
|
|
|
|
bg: true,
|
|
|
|
|
opt: 'image',
|
|
|
|
|
style: hideBackgroundStyle,
|
2016-10-01 16:34:25 +00:00
|
|
|
});
|
|
|
|
|
}
|
2014-07-13 00:32:44 +00:00
|
|
|
}
|
2016-10-01 16:34:25 +00:00
|
|
|
|
2014-09-28 16:05:46 +00:00
|
|
|
return out;
|
2016-10-01 16:34:25 +00:00
|
|
|
};
|
2014-09-28 16:05:46 +00:00
|
|
|
|
2016-10-01 16:34:25 +00:00
|
|
|
// Cosmetic filters: these are straight CSS selectors.
|
2019-02-18 21:00:42 +00:00
|
|
|
//
|
|
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/389
|
|
|
|
|
// Test filter using comma-separated list to better detect invalid CSS
|
|
|
|
|
// selectors.
|
2019-07-26 13:24:34 +00:00
|
|
|
//
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2515
|
|
|
|
|
// Remove trailing pseudo-element when querying.
|
2019-06-29 15:06:03 +00:00
|
|
|
const fromPlainCosmeticFilter = function(raw) {
|
2018-11-06 18:11:03 +00:00
|
|
|
let elems;
|
2016-10-01 16:34:25 +00:00
|
|
|
try {
|
2019-06-29 15:06:03 +00:00
|
|
|
document.documentElement.matches(`${raw},\na`);
|
2019-07-26 13:24:34 +00:00
|
|
|
elems = document.querySelectorAll(
|
|
|
|
|
raw.replace(rePseudoElements, '')
|
|
|
|
|
);
|
2025-01-09 14:33:22 +00:00
|
|
|
} catch {
|
2016-12-25 21:56:39 +00:00
|
|
|
return;
|
2016-10-01 16:34:25 +00:00
|
|
|
}
|
2018-11-06 18:11:03 +00:00
|
|
|
const out = [];
|
2018-11-06 18:20:44 +00:00
|
|
|
for ( const elem of elems ) {
|
2023-12-20 16:23:25 +00:00
|
|
|
if ( elem === pickerFrame ) { continue; }
|
2020-09-07 12:28:01 +00:00
|
|
|
out.push({ elem, raw, style: vAPI.hideStyle });
|
2016-10-01 16:34:25 +00:00
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
};
|
2015-03-05 17:52:12 +00:00
|
|
|
|
2016-10-01 16:34:25 +00:00
|
|
|
// https://github.com/gorhill/uBlock/issues/1772
|
2019-06-29 15:06:03 +00:00
|
|
|
// Handle procedural cosmetic filters.
|
2019-07-26 13:24:34 +00:00
|
|
|
//
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2515
|
|
|
|
|
// Remove trailing pseudo-element when querying.
|
2019-02-18 21:00:42 +00:00
|
|
|
const fromCompiledCosmeticFilter = function(raw) {
|
2021-07-12 15:55:58 +00:00
|
|
|
if ( noCosmeticFiltering ) { return; }
|
2016-12-25 21:56:39 +00:00
|
|
|
if ( typeof raw !== 'string' ) { return; }
|
2020-09-07 12:28:01 +00:00
|
|
|
let elems, style;
|
2016-12-25 21:56:39 +00:00
|
|
|
try {
|
2019-07-26 13:24:34 +00:00
|
|
|
const o = JSON.parse(raw);
|
2020-09-07 12:28:01 +00:00
|
|
|
elems = vAPI.domFilterer.createProceduralFilter(o).exec();
|
2022-12-10 16:18:24 +00:00
|
|
|
switch ( o.action && o.action[0] || '' ) {
|
|
|
|
|
case '':
|
|
|
|
|
case 'remove':
|
|
|
|
|
style = vAPI.hideStyle;
|
|
|
|
|
break;
|
|
|
|
|
case 'style':
|
|
|
|
|
style = o.action[1];
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-01-09 14:33:22 +00:00
|
|
|
} catch {
|
2016-12-25 21:56:39 +00:00
|
|
|
return;
|
2016-10-01 16:34:25 +00:00
|
|
|
}
|
2016-12-25 21:56:39 +00:00
|
|
|
if ( !elems ) { return; }
|
2019-02-18 21:00:42 +00:00
|
|
|
const out = [];
|
|
|
|
|
for ( const elem of elems ) {
|
2020-09-07 12:28:01 +00:00
|
|
|
out.push({ elem, raw, style });
|
2016-12-25 21:56:39 +00:00
|
|
|
}
|
|
|
|
|
return out;
|
2016-10-01 16:34:25 +00:00
|
|
|
};
|
|
|
|
|
|
2020-09-07 12:28:01 +00:00
|
|
|
vAPI.epickerStyleProxies = vAPI.epickerStyleProxies || new Map();
|
|
|
|
|
|
2020-09-01 16:32:12 +00:00
|
|
|
let lastFilter;
|
|
|
|
|
let lastResultset;
|
|
|
|
|
let previewing = false;
|
2016-10-01 16:34:25 +00:00
|
|
|
|
2020-09-01 16:32:12 +00:00
|
|
|
const queryAll = function(details) {
|
|
|
|
|
let { filter, compiled } = details;
|
2016-10-01 16:34:25 +00:00
|
|
|
filter = filter.trim();
|
2020-09-01 16:32:12 +00:00
|
|
|
if ( filter === lastFilter ) { return lastResultset; }
|
2016-10-01 16:34:25 +00:00
|
|
|
unapply();
|
2020-09-01 16:32:12 +00:00
|
|
|
if ( filter === '' || filter === '!' ) {
|
2016-10-01 16:34:25 +00:00
|
|
|
lastFilter = '';
|
2020-09-07 12:28:01 +00:00
|
|
|
lastResultset = undefined;
|
|
|
|
|
return;
|
2015-03-05 17:52:12 +00:00
|
|
|
}
|
2016-12-25 21:56:39 +00:00
|
|
|
lastFilter = filter;
|
2020-11-29 16:31:20 +00:00
|
|
|
if ( reCosmeticAnchor.test(filter) === false ) {
|
2016-12-25 21:56:39 +00:00
|
|
|
lastResultset = fromNetworkFilter(filter);
|
|
|
|
|
if ( previewing ) { apply(); }
|
2020-09-01 16:32:12 +00:00
|
|
|
return lastResultset;
|
2016-12-25 21:56:39 +00:00
|
|
|
}
|
2020-09-01 16:32:12 +00:00
|
|
|
lastResultset = fromPlainCosmeticFilter(compiled);
|
2016-12-25 21:56:39 +00:00
|
|
|
if ( lastResultset ) {
|
|
|
|
|
if ( previewing ) { apply(); }
|
2020-09-01 16:32:12 +00:00
|
|
|
return lastResultset;
|
2016-12-25 21:56:39 +00:00
|
|
|
}
|
|
|
|
|
// Procedural cosmetic filter
|
2020-09-01 16:32:12 +00:00
|
|
|
lastResultset = fromCompiledCosmeticFilter(compiled);
|
2019-09-17 19:15:01 +00:00
|
|
|
if ( previewing ) { apply(); }
|
2020-09-01 16:32:12 +00:00
|
|
|
return lastResultset;
|
2016-10-01 16:34:25 +00:00
|
|
|
};
|
|
|
|
|
|
2020-09-07 12:28:01 +00:00
|
|
|
const apply = function() {
|
|
|
|
|
unapply();
|
|
|
|
|
if ( Array.isArray(lastResultset) === false ) { return; }
|
|
|
|
|
const rootElem = document.documentElement;
|
|
|
|
|
for ( const { elem, style } of lastResultset ) {
|
2023-12-20 16:23:25 +00:00
|
|
|
if ( elem === pickerFrame ) { continue; }
|
2022-12-10 16:18:24 +00:00
|
|
|
if ( style === undefined ) { continue; }
|
2020-09-07 12:28:01 +00:00
|
|
|
if ( elem === rootElem && style === vAPI.hideStyle ) { continue; }
|
|
|
|
|
let styleToken = vAPI.epickerStyleProxies.get(style);
|
|
|
|
|
if ( styleToken === undefined ) {
|
|
|
|
|
styleToken = vAPI.randomToken();
|
|
|
|
|
vAPI.epickerStyleProxies.set(style, styleToken);
|
|
|
|
|
vAPI.userStylesheet.add(`[${styleToken}]\n{${style}}`, true);
|
2016-10-01 16:34:25 +00:00
|
|
|
}
|
2020-09-07 12:28:01 +00:00
|
|
|
elem.setAttribute(styleToken, '');
|
2016-04-15 16:27:53 +00:00
|
|
|
}
|
2016-10-01 16:34:25 +00:00
|
|
|
};
|
|
|
|
|
|
2019-02-18 21:00:42 +00:00
|
|
|
const unapply = function() {
|
2020-09-07 12:28:01 +00:00
|
|
|
for ( const styleToken of vAPI.epickerStyleProxies.values() ) {
|
|
|
|
|
for ( const elem of document.querySelectorAll(`[${styleToken}]`) ) {
|
|
|
|
|
elem.removeAttribute(styleToken);
|
|
|
|
|
}
|
2016-10-01 16:34:25 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-29 15:06:03 +00: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.
|
2020-09-01 16:32:12 +00:00
|
|
|
const preview = function(state, permanent = false) {
|
|
|
|
|
previewing = state !== false;
|
2019-06-29 15:06:03 +00:00
|
|
|
if ( previewing === false ) {
|
|
|
|
|
return unapply();
|
|
|
|
|
}
|
2020-09-07 12:28:01 +00:00
|
|
|
if ( Array.isArray(lastResultset) === false ) { return; }
|
2020-11-29 16:31:20 +00:00
|
|
|
if ( permanent === false || reCosmeticAnchor.test(lastFilter) === false ) {
|
2020-09-07 12:28:01 +00:00
|
|
|
return apply();
|
|
|
|
|
}
|
2021-07-12 15:55:58 +00:00
|
|
|
if ( noCosmeticFiltering ) { return; }
|
2020-09-01 16:32:12 +00:00
|
|
|
const cssSelectors = new Set();
|
|
|
|
|
const proceduralSelectors = new Set();
|
2020-09-07 12:28:01 +00:00
|
|
|
for ( const { raw } of lastResultset ) {
|
|
|
|
|
if ( raw.startsWith('{') ) {
|
|
|
|
|
proceduralSelectors.add(raw);
|
2020-09-01 16:32:12 +00:00
|
|
|
} else {
|
2020-09-07 12:28:01 +00:00
|
|
|
cssSelectors.add(raw);
|
2019-06-30 14:22:06 +00:00
|
|
|
}
|
2016-12-25 21:56:39 +00:00
|
|
|
}
|
2020-09-01 16:32:12 +00:00
|
|
|
if ( cssSelectors.size !== 0 ) {
|
2021-02-17 14:12:00 +00:00
|
|
|
vAPI.domFilterer.addCSS(
|
|
|
|
|
`${Array.from(cssSelectors).join('\n')}\n{${vAPI.hideStyle}}`,
|
2020-10-13 11:19:06 +00:00
|
|
|
{ mustInject: true }
|
2020-09-01 16:32:12 +00:00
|
|
|
);
|
2016-12-25 21:56:39 +00:00
|
|
|
}
|
2020-09-01 16:32:12 +00:00
|
|
|
if ( proceduralSelectors.size !== 0 ) {
|
|
|
|
|
vAPI.domFilterer.addProceduralSelectors(
|
|
|
|
|
Array.from(proceduralSelectors)
|
|
|
|
|
);
|
2016-10-01 16:34:25 +00:00
|
|
|
}
|
2016-12-25 21:56:39 +00:00
|
|
|
};
|
|
|
|
|
|
2020-09-07 12:28:01 +00:00
|
|
|
return { preview, queryAll };
|
2016-12-25 21:56:39 +00:00
|
|
|
})();
|
2014-07-13 00:32:44 +00:00
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2023-07-14 18:19:30 +00:00
|
|
|
const onOptimizeCandidates = function(details) {
|
2020-10-16 21:12:22 +00:00
|
|
|
const { candidates } = details;
|
|
|
|
|
const results = [];
|
|
|
|
|
for ( const paths of candidates ) {
|
|
|
|
|
let count = Number.MAX_SAFE_INTEGER;
|
|
|
|
|
let selector = '';
|
|
|
|
|
for ( let i = 0, n = paths.length; i < n; i++ ) {
|
|
|
|
|
const s = paths.slice(n - i - 1).join('');
|
|
|
|
|
const elems = document.querySelectorAll(s);
|
|
|
|
|
if ( elems.length < count ) {
|
|
|
|
|
selector = s;
|
|
|
|
|
count = elems.length;
|
|
|
|
|
}
|
2020-10-06 21:26:28 +00:00
|
|
|
}
|
2020-10-16 21:12:22 +00:00
|
|
|
results.push({ selector: `##${selector}`, count });
|
|
|
|
|
}
|
|
|
|
|
// Sort by most match count and shortest selector to least match count and
|
|
|
|
|
// longest selector.
|
|
|
|
|
results.sort((a, b) => {
|
|
|
|
|
const r = b.count - a.count;
|
|
|
|
|
if ( r !== 0 ) { return r; }
|
|
|
|
|
return a.selector.length - b.selector.length;
|
|
|
|
|
});
|
2024-04-10 14:28:11 +00:00
|
|
|
|
2023-12-03 21:21:32 +00:00
|
|
|
pickerFramePort.postMessage({
|
2020-10-16 21:12:22 +00:00
|
|
|
what: 'candidatesOptimized',
|
|
|
|
|
candidates: results.map(a => a.selector),
|
2020-10-20 09:37:07 +00:00
|
|
|
slot: details.slot,
|
2020-10-06 21:26:28 +00:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2018-12-22 20:44:23 +00:00
|
|
|
const showDialog = function(options) {
|
2023-12-03 21:21:32 +00:00
|
|
|
pickerFramePort.postMessage({
|
2020-09-01 16:32:12 +00:00
|
|
|
what: 'showDialog',
|
2020-10-07 15:52:38 +00:00
|
|
|
url: self.location.href,
|
2020-09-01 16:32:12 +00:00
|
|
|
netFilters: netFilterCandidates,
|
|
|
|
|
cosmeticFilters: cosmeticFilterCandidates,
|
|
|
|
|
filter: bestCandidateFilter,
|
|
|
|
|
options,
|
|
|
|
|
});
|
2014-07-13 00:32:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
const elementFromPoint = (( ) => {
|
|
|
|
|
let lastX, lastY;
|
2017-05-27 15:51:24 +00:00
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
return (x, y) => {
|
2017-05-27 15:51:24 +00:00
|
|
|
if ( x !== undefined ) {
|
|
|
|
|
lastX = x; lastY = y;
|
|
|
|
|
} else if ( lastX !== undefined ) {
|
|
|
|
|
x = lastX; y = lastY;
|
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2023-12-20 16:23:25 +00:00
|
|
|
if ( !pickerFrame ) { return null; }
|
|
|
|
|
const magicAttr = `${pickerUniqueId}-clickblind`;
|
|
|
|
|
pickerFrame.setAttribute(magicAttr, '');
|
2019-06-29 15:06:03 +00:00
|
|
|
let elem = document.elementFromPoint(x, y);
|
2021-07-12 15:55:58 +00:00
|
|
|
if (
|
|
|
|
|
elem === null || /* to skip following tests */
|
|
|
|
|
elem === document.body ||
|
|
|
|
|
elem === document.documentElement || (
|
|
|
|
|
pickerBootArgs.zap !== true &&
|
|
|
|
|
noCosmeticFiltering &&
|
|
|
|
|
resourceURLsFromElement(elem).length === 0
|
|
|
|
|
)
|
|
|
|
|
) {
|
2017-05-27 15:51:24 +00:00
|
|
|
elem = null;
|
|
|
|
|
}
|
2019-01-15 21:34:57 +00:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/380
|
2023-12-20 16:23:25 +00:00
|
|
|
pickerFrame.removeAttribute(magicAttr);
|
2017-05-27 15:51:24 +00:00
|
|
|
return elem;
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
const highlightElementAtPoint = function(mx, my) {
|
|
|
|
|
const elem = elementFromPoint(mx, my);
|
|
|
|
|
highlightElements(elem ? [ elem ] : []);
|
|
|
|
|
};
|
2015-05-27 19:15:20 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
/******************************************************************************/
|
2017-05-29 14:38:22 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
const filterElementAtPoint = function(mx, my, broad) {
|
|
|
|
|
if ( filtersFrom(mx, my) === 0 ) { return; }
|
|
|
|
|
showDialog({ broad });
|
|
|
|
|
};
|
2017-10-28 13:07:41 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
/******************************************************************************/
|
2017-10-28 13:07:41 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
// https://www.reddit.com/r/uBlockOrigin/comments/bktxtb/scrolling_doesnt_work/emn901o
|
|
|
|
|
// Override 'fixed' position property on body element if present.
|
2017-10-28 13:07:41 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
// With touch-driven devices, first highlight the element and remove only
|
|
|
|
|
// when tapping again the highlighted area.
|
2015-05-27 19:15:20 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
const zapElementAtPoint = function(mx, my, options) {
|
|
|
|
|
if ( options.highlight ) {
|
|
|
|
|
const elem = elementFromPoint(mx, my);
|
|
|
|
|
if ( elem ) {
|
|
|
|
|
highlightElements([ elem ]);
|
2017-10-28 13:07:41 +00:00
|
|
|
}
|
2020-09-03 14:27:35 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2014-07-13 00:32:44 +00:00
|
|
|
|
2020-11-18 19:11:36 +00:00
|
|
|
let elemToRemove = targetElements.length !== 0 && targetElements[0] || null;
|
|
|
|
|
if ( elemToRemove === null && mx !== undefined ) {
|
|
|
|
|
elemToRemove = elementFromPoint(mx, my);
|
2020-09-03 14:27:35 +00:00
|
|
|
}
|
2014-07-13 00:32:44 +00:00
|
|
|
|
2020-11-18 19:11:36 +00:00
|
|
|
if ( elemToRemove instanceof Element === false ) { return; }
|
2017-11-12 12:44:28 +00:00
|
|
|
|
2022-01-07 13:54:23 +00:00
|
|
|
const getStyleValue = (elem, prop) => {
|
2020-09-03 14:27:35 +00:00
|
|
|
const style = window.getComputedStyle(elem);
|
|
|
|
|
return style ? style[prop] : '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Heuristic to detect scroll-locking: remove such lock when detected.
|
2022-01-07 13:54:23 +00:00
|
|
|
let maybeScrollLocked = elemToRemove.shadowRoot instanceof DocumentFragment;
|
|
|
|
|
if ( maybeScrollLocked === false ) {
|
|
|
|
|
let elem = elemToRemove;
|
|
|
|
|
do {
|
|
|
|
|
maybeScrollLocked =
|
|
|
|
|
parseInt(getStyleValue(elem, 'zIndex'), 10) >= 1000 ||
|
|
|
|
|
getStyleValue(elem, 'position') === 'fixed';
|
|
|
|
|
elem = elem.parentElement;
|
|
|
|
|
} while ( elem !== null && maybeScrollLocked === false );
|
|
|
|
|
}
|
2020-11-18 19:11:36 +00:00
|
|
|
if ( maybeScrollLocked ) {
|
2020-09-03 14:27:35 +00:00
|
|
|
const doc = document;
|
|
|
|
|
if ( getStyleValue(doc.body, 'overflowY') === 'hidden' ) {
|
|
|
|
|
doc.body.style.setProperty('overflow', 'auto', 'important');
|
2017-05-29 14:38:22 +00:00
|
|
|
}
|
2020-09-03 14:27:35 +00:00
|
|
|
if ( getStyleValue(doc.body, 'position') === 'fixed' ) {
|
2020-11-18 19:11:36 +00:00
|
|
|
doc.body.style.setProperty('position', 'initial', 'important');
|
|
|
|
|
}
|
|
|
|
|
if ( getStyleValue(doc.documentElement, 'position') === 'fixed' ) {
|
|
|
|
|
doc.documentElement.style.setProperty('position', 'initial', 'important');
|
2017-05-29 14:38:22 +00:00
|
|
|
}
|
2020-09-03 14:27:35 +00:00
|
|
|
if ( getStyleValue(doc.documentElement, 'overflowY') === 'hidden' ) {
|
|
|
|
|
doc.documentElement.style.setProperty('overflow', 'auto', 'important');
|
2016-10-01 16:34:25 +00:00
|
|
|
}
|
2014-09-28 18:38:17 +00:00
|
|
|
}
|
2020-11-18 19:11:36 +00:00
|
|
|
elemToRemove.remove();
|
2020-09-03 14:27:35 +00:00
|
|
|
highlightElementAtPoint(mx, my);
|
2015-02-09 13:03:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2019-06-29 15:06:03 +00:00
|
|
|
const onKeyPressed = function(ev) {
|
2017-05-27 15:51:24 +00: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 19:28:00 +00:00
|
|
|
if (
|
|
|
|
|
(ev.key === 'Delete' || ev.key === 'Backspace') &&
|
2020-09-01 16:32:12 +00:00
|
|
|
pickerBootArgs.zap
|
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 19:28:00 +00:00
|
|
|
) {
|
2017-05-27 15:51:24 +00:00
|
|
|
ev.stopPropagation();
|
|
|
|
|
ev.preventDefault();
|
2020-09-03 14:27:35 +00:00
|
|
|
zapElementAtPoint();
|
2017-05-27 15:51:24 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Esc
|
|
|
|
|
if ( ev.key === 'Escape' || ev.which === 27 ) {
|
2014-07-13 15:38:52 +00:00
|
|
|
ev.stopPropagation();
|
|
|
|
|
ev.preventDefault();
|
2016-12-25 21:56:39 +00:00
|
|
|
filterToDOMInterface.preview(false);
|
2020-09-03 14:27:35 +00:00
|
|
|
quitPicker();
|
2017-05-27 15:51:24 +00:00
|
|
|
return;
|
2014-07-13 15:38:52 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2015-04-07 01:26:05 +00:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/190
|
2020-09-01 16:32:12 +00:00
|
|
|
// May need to dynamically adjust the height of the overlay + new position
|
|
|
|
|
// of highlighted elements.
|
2014-08-30 21:22:31 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
const onViewportChanged = function() {
|
2014-08-30 21:20:14 +00:00
|
|
|
highlightElements(targetElements, true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2020-09-01 16:32:12 +00:00
|
|
|
// Auto-select a specific target, if any, and if possible
|
2015-02-08 18:34:28 +00:00
|
|
|
|
2020-09-01 16:32:12 +00:00
|
|
|
const startPicker = function() {
|
2023-12-20 16:23:25 +00:00
|
|
|
pickerFrame.focus();
|
2020-09-11 12:12:45 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
self.addEventListener('scroll', onViewportChanged, { passive: true });
|
|
|
|
|
self.addEventListener('resize', onViewportChanged, { passive: true });
|
|
|
|
|
self.addEventListener('keydown', onKeyPressed, true);
|
2015-02-09 13:03:29 +00:00
|
|
|
|
2015-02-08 18:34:28 +00:00
|
|
|
// Try using mouse position
|
2019-09-18 16:17:45 +00:00
|
|
|
if (
|
2020-09-01 16:32:12 +00:00
|
|
|
pickerBootArgs.mouse &&
|
2021-07-12 15:55:58 +00:00
|
|
|
vAPI.mouseClick instanceof Object &&
|
2019-09-18 16:17:45 +00:00
|
|
|
typeof vAPI.mouseClick.x === 'number' &&
|
|
|
|
|
vAPI.mouseClick.x > 0
|
|
|
|
|
) {
|
|
|
|
|
if ( filtersFrom(vAPI.mouseClick.x, vAPI.mouseClick.y) !== 0 ) {
|
2020-09-03 14:27:35 +00:00
|
|
|
return showDialog();
|
2015-02-08 18:34:28 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No mouse position available, use suggested target
|
2020-09-01 16:32:12 +00:00
|
|
|
const target = pickerBootArgs.target || '';
|
2018-11-06 18:11:03 +00:00
|
|
|
const pos = target.indexOf('\t');
|
2018-08-15 11:58:42 +00:00
|
|
|
if ( pos === -1 ) { return; }
|
|
|
|
|
|
2018-11-06 18:11:03 +00:00
|
|
|
const srcAttrMap = {
|
2015-02-08 18:34:28 +00:00
|
|
|
'a': 'href',
|
2016-04-15 16:27:53 +00:00
|
|
|
'audio': 'src',
|
2015-02-10 15:50:47 +00:00
|
|
|
'embed': 'src',
|
2016-04-15 16:27:53 +00:00
|
|
|
'iframe': 'src',
|
|
|
|
|
'img': 'src',
|
2015-02-08 18:34:28 +00:00
|
|
|
'video': 'src',
|
|
|
|
|
};
|
2018-11-06 18:11:03 +00: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 18:20:44 +00:00
|
|
|
for ( const elem of elems ) {
|
2023-12-20 16:23:25 +00:00
|
|
|
if ( elem === pickerFrame ) { continue; }
|
2021-07-12 22:32:32 +00:00
|
|
|
const srcs = resourceURLsFromElement(elem);
|
|
|
|
|
if (
|
|
|
|
|
(srcs.length !== 0 && srcs.includes(url) === false) ||
|
|
|
|
|
(srcs.length === 0 && url !== 'about:blank')
|
|
|
|
|
) {
|
2018-11-06 18:11:03 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
2016-04-15 16:27:53 +00:00
|
|
|
filtersFrom(elem);
|
2021-07-12 15:55:58 +00:00
|
|
|
if (
|
|
|
|
|
netFilterCandidates.length !== 0 ||
|
|
|
|
|
cosmeticFilterCandidates.length !== 0
|
|
|
|
|
) {
|
2021-07-12 22:32:32 +00:00
|
|
|
if ( pickerBootArgs.mouse !== true ) {
|
|
|
|
|
elem.scrollIntoView({
|
|
|
|
|
behavior: 'smooth',
|
|
|
|
|
block: 'center',
|
|
|
|
|
inline: 'center'
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-07-12 15:55:58 +00:00
|
|
|
showDialog({ broad: true });
|
|
|
|
|
}
|
|
|
|
|
return;
|
2015-02-08 18:34:28 +00:00
|
|
|
}
|
2015-06-17 17:49:43 +00:00
|
|
|
|
|
|
|
|
// A target was specified, but it wasn't found: abort.
|
2020-09-03 14:27:35 +00:00
|
|
|
quitPicker();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
// Let's have the element picker code flushed from memory when no longer
|
|
|
|
|
// in use: to ensure this, release all local references.
|
|
|
|
|
|
|
|
|
|
const quitPicker = function() {
|
|
|
|
|
self.removeEventListener('scroll', onViewportChanged, { passive: true });
|
|
|
|
|
self.removeEventListener('resize', onViewportChanged, { passive: true });
|
|
|
|
|
self.removeEventListener('keydown', onKeyPressed, true);
|
|
|
|
|
vAPI.shutdown.remove(quitPicker);
|
2023-12-20 16:23:25 +00:00
|
|
|
if ( pickerFramePort ) {
|
2023-12-16 16:23:58 +00:00
|
|
|
pickerFramePort.close();
|
|
|
|
|
pickerFramePort = null;
|
|
|
|
|
}
|
2023-12-20 16:23:25 +00:00
|
|
|
if ( pickerFrame ) {
|
|
|
|
|
pickerFrame.remove();
|
|
|
|
|
pickerFrame = null;
|
2023-12-16 16:23:58 +00:00
|
|
|
}
|
2020-09-03 14:27:35 +00:00
|
|
|
vAPI.userStylesheet.remove(pickerCSS);
|
|
|
|
|
vAPI.userStylesheet.apply();
|
2023-12-20 16:23:25 +00:00
|
|
|
vAPI.pickerFrame = false;
|
2020-09-03 14:27:35 +00:00
|
|
|
self.focus();
|
2015-02-08 18:34:28 +00:00
|
|
|
};
|
|
|
|
|
|
2023-12-20 16:23:25 +00:00
|
|
|
vAPI.shutdown.add(quitPicker);
|
|
|
|
|
|
2015-02-08 18:34:28 +00:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2020-09-01 16:32:12 +00:00
|
|
|
const onDialogMessage = function(msg) {
|
|
|
|
|
switch ( msg.what ) {
|
2024-04-10 14:28:11 +00:00
|
|
|
case 'start':
|
|
|
|
|
startPicker();
|
|
|
|
|
if ( pickerFramePort === null ) { break; }
|
|
|
|
|
if ( targetElements.length === 0 ) {
|
|
|
|
|
highlightElements([], true);
|
2020-09-03 14:27:35 +00:00
|
|
|
}
|
2024-04-10 14:28:11 +00:00
|
|
|
break;
|
|
|
|
|
case 'optimizeCandidates':
|
|
|
|
|
onOptimizeCandidates(msg);
|
|
|
|
|
break;
|
|
|
|
|
case 'dialogCreate':
|
|
|
|
|
filterToDOMInterface.queryAll(msg);
|
|
|
|
|
filterToDOMInterface.preview(true, true);
|
|
|
|
|
quitPicker();
|
|
|
|
|
break;
|
|
|
|
|
case 'dialogSetFilter': {
|
|
|
|
|
const resultset = filterToDOMInterface.queryAll(msg) || [];
|
|
|
|
|
highlightElements(resultset.map(a => a.elem), true);
|
|
|
|
|
if ( msg.filter === '!' ) { break; }
|
|
|
|
|
pickerFramePort.postMessage({
|
|
|
|
|
what: 'resultsetDetails',
|
|
|
|
|
count: resultset.length,
|
|
|
|
|
opt: resultset.length !== 0 ? resultset[0].opt : undefined,
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'quitPicker':
|
|
|
|
|
filterToDOMInterface.preview(false);
|
|
|
|
|
quitPicker();
|
|
|
|
|
break;
|
|
|
|
|
case 'highlightElementAtPoint':
|
|
|
|
|
highlightElementAtPoint(msg.mx, msg.my);
|
|
|
|
|
break;
|
|
|
|
|
case 'unhighlight':
|
|
|
|
|
highlightElements([]);
|
|
|
|
|
break;
|
|
|
|
|
case 'filterElementAtPoint':
|
|
|
|
|
filterElementAtPoint(msg.mx, msg.my, msg.broad);
|
|
|
|
|
break;
|
|
|
|
|
case 'zapElementAtPoint':
|
|
|
|
|
zapElementAtPoint(msg.mx, msg.my, msg.options);
|
|
|
|
|
if ( msg.options.highlight !== true && msg.options.stay !== true ) {
|
2020-09-03 14:27:35 +00:00
|
|
|
quitPicker();
|
2024-04-10 14:28:11 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'togglePreview':
|
|
|
|
|
filterToDOMInterface.preview(msg.state);
|
|
|
|
|
if ( msg.state === false ) {
|
|
|
|
|
highlightElements(targetElements, true);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2020-09-01 16:32:12 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
// epicker-ui.html will be injected in the page through an iframe, and
|
|
|
|
|
// is a sandboxed so as to prevent the page from interfering with its
|
|
|
|
|
// content and behavior.
|
|
|
|
|
//
|
|
|
|
|
// The purpose of epicker.js is to:
|
|
|
|
|
// - Install the element picker UI, and wait for the component to establish
|
|
|
|
|
// a direct communication channel.
|
|
|
|
|
// - Lookup candidate filters from elements at a specific position.
|
|
|
|
|
// - Highlight element(s) at a specific position or according to whether
|
|
|
|
|
// they match candidate filters;
|
|
|
|
|
// - Preview the result of applying a candidate filter;
|
|
|
|
|
//
|
|
|
|
|
// When the element picker is installed on a page, the only change the page
|
|
|
|
|
// sees is an iframe with a random attribute. The page can't see the content
|
|
|
|
|
// of the iframe, and cannot interfere with its style properties. However the
|
|
|
|
|
// page can remove the iframe.
|
|
|
|
|
|
|
|
|
|
// The DOM filterer will not be present when cosmetic filtering is disabled.
|
2021-07-12 15:55:58 +00:00
|
|
|
const noCosmeticFiltering =
|
|
|
|
|
vAPI.domFilterer instanceof Object === false ||
|
|
|
|
|
vAPI.noSpecificCosmeticFiltering === true;
|
2017-10-21 17:43:46 +00:00
|
|
|
|
2020-09-03 14:27:35 +00:00
|
|
|
// https://github.com/gorhill/uBlock/issues/1529
|
|
|
|
|
// In addition to inline styles, harden the element picker styles by using
|
|
|
|
|
// dedicated CSS rules.
|
2018-12-20 22:29:39 +00:00
|
|
|
const pickerCSSStyle = [
|
2017-10-22 12:59:29 +00:00
|
|
|
'background: transparent',
|
|
|
|
|
'border: 0',
|
|
|
|
|
'border-radius: 0',
|
|
|
|
|
'box-shadow: none',
|
2022-11-17 14:25:20 +00:00
|
|
|
'color-scheme: light dark',
|
2017-10-22 12:59:29 +00:00
|
|
|
'display: block',
|
2022-04-04 13:17:28 +00:00
|
|
|
'filter: none',
|
2020-09-03 14:27:35 +00:00
|
|
|
'height: 100vh',
|
2024-03-06 13:45:49 +00:00
|
|
|
' height: 100svh',
|
2017-10-22 12:59:29 +00:00
|
|
|
'left: 0',
|
|
|
|
|
'margin: 0',
|
|
|
|
|
'max-height: none',
|
|
|
|
|
'max-width: none',
|
2020-04-15 13:46:12 +00:00
|
|
|
'min-height: unset',
|
|
|
|
|
'min-width: unset',
|
2017-10-22 12:59:29 +00:00
|
|
|
'opacity: 1',
|
|
|
|
|
'outline: 0',
|
|
|
|
|
'padding: 0',
|
2020-09-03 14:27:35 +00:00
|
|
|
'pointer-events: auto',
|
2017-10-22 12:59:29 +00:00
|
|
|
'position: fixed',
|
|
|
|
|
'top: 0',
|
2022-04-28 13:55:52 +00:00
|
|
|
'transform: none',
|
2022-09-11 11:34:29 +00:00
|
|
|
'visibility: hidden',
|
2017-10-22 12:59:29 +00:00
|
|
|
'width: 100%',
|
|
|
|
|
'z-index: 2147483647',
|
2022-09-11 11:34:29 +00:00
|
|
|
''
|
2023-12-03 21:21:32 +00:00
|
|
|
].join(' !important;\n');
|
2020-12-21 14:20:56 +00:00
|
|
|
|
2022-09-10 14:07:16 +00:00
|
|
|
|
2020-09-01 16:32:12 +00:00
|
|
|
const pickerCSS = `
|
2023-12-20 16:23:25 +00:00
|
|
|
:root > [${pickerUniqueId}] {
|
2023-12-03 21:21:32 +00:00
|
|
|
${pickerCSSStyle}
|
2020-09-01 16:32:12 +00:00
|
|
|
}
|
2023-12-20 16:23:25 +00:00
|
|
|
:root > [${pickerUniqueId}-loaded] {
|
2022-09-11 11:34:29 +00:00
|
|
|
visibility: visible !important;
|
|
|
|
|
}
|
2023-12-20 16:23:25 +00:00
|
|
|
:root [${pickerUniqueId}-clickblind] {
|
2020-09-01 16:32:12 +00:00
|
|
|
pointer-events: none !important;
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
vAPI.userStylesheet.add(pickerCSS);
|
2017-10-22 12:59:29 +00:00
|
|
|
vAPI.userStylesheet.apply();
|
2016-04-03 12:34:28 +00:00
|
|
|
|
2023-12-20 16:23:25 +00:00
|
|
|
let pickerBootArgs;
|
2023-12-16 16:23:58 +00:00
|
|
|
let pickerFramePort = null;
|
2020-09-01 16:32:12 +00:00
|
|
|
|
2023-12-20 16:23:25 +00:00
|
|
|
const bootstrap = async ( ) => {
|
|
|
|
|
pickerBootArgs = await vAPI.messaging.send('elementPicker', {
|
|
|
|
|
what: 'elementPickerArguments',
|
|
|
|
|
});
|
|
|
|
|
if ( typeof pickerBootArgs !== 'object' ) { return; }
|
|
|
|
|
if ( pickerBootArgs === null ) { return; }
|
|
|
|
|
// Restore net filter union data if origin is the same.
|
|
|
|
|
const eprom = pickerBootArgs.eprom || null;
|
|
|
|
|
if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) {
|
|
|
|
|
lastNetFilterHostname = eprom.lastNetFilterHostname || '';
|
|
|
|
|
lastNetFilterUnion = eprom.lastNetFilterUnion || '';
|
|
|
|
|
}
|
2020-09-03 14:27:35 +00:00
|
|
|
const url = new URL(pickerBootArgs.pickerURL);
|
|
|
|
|
if ( pickerBootArgs.zap ) {
|
|
|
|
|
url.searchParams.set('zap', '1');
|
|
|
|
|
}
|
2023-12-20 16:23:25 +00:00
|
|
|
return new Promise(resolve => {
|
|
|
|
|
const iframe = document.createElement('iframe');
|
|
|
|
|
iframe.setAttribute(pickerUniqueId, '');
|
|
|
|
|
document.documentElement.append(iframe);
|
|
|
|
|
iframe.addEventListener('load', ( ) => {
|
|
|
|
|
iframe.setAttribute(`${pickerUniqueId}-loaded`, '');
|
|
|
|
|
const channel = new MessageChannel();
|
|
|
|
|
pickerFramePort = channel.port1;
|
|
|
|
|
pickerFramePort.onmessage = ev => {
|
|
|
|
|
onDialogMessage(ev.data || {});
|
|
|
|
|
};
|
|
|
|
|
pickerFramePort.onmessageerror = ( ) => {
|
|
|
|
|
quitPicker();
|
|
|
|
|
};
|
|
|
|
|
iframe.contentWindow.postMessage(
|
|
|
|
|
{ what: 'epickerStart' },
|
|
|
|
|
url.href,
|
|
|
|
|
[ channel.port2 ]
|
|
|
|
|
);
|
|
|
|
|
resolve(iframe);
|
|
|
|
|
}, { once: true });
|
|
|
|
|
iframe.contentWindow.location = url.href;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let pickerFrame = await bootstrap();
|
|
|
|
|
if ( Boolean(pickerFrame) === false ) {
|
|
|
|
|
quitPicker();
|
2020-09-03 14:27:35 +00:00
|
|
|
}
|
2015-01-18 07:12:39 +00:00
|
|
|
|
2014-07-13 00:32:44 +00:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
})();
|
2018-05-03 13:55:36 +00: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;
|