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
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-08 19:34:28 +01:00
|
|
|
|
var pickerRoot = document.getElementById(vAPI.sessionId);
|
2014-10-23 14:12:37 +02:00
|
|
|
|
if ( pickerRoot ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
var pickerBody = null;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var svgOcean = null;
|
|
|
|
|
var svgIslands = null;
|
2015-03-09 15:11:22 +01:00
|
|
|
|
var svgRoot = null;
|
2015-02-09 14:03:29 +01:00
|
|
|
|
var dialog = null;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var taCandidate = null;
|
2014-09-28 18:05:46 +02:00
|
|
|
|
|
|
|
|
|
var netFilterCandidates = [];
|
|
|
|
|
var cosmeticFilterCandidates = [];
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
var targetElements = [];
|
2016-04-16 17:20:01 +02:00
|
|
|
|
var candidateElements = [];
|
|
|
|
|
var bestCandidateFilter = null;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2015-03-20 16:39:20 +01:00
|
|
|
|
var lastNetFilterSession = window.location.host + window.location.pathname;
|
|
|
|
|
var lastNetFilterHostname = '';
|
|
|
|
|
var lastNetFilterUnion = '';
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
var cssScope = ':scope > ';
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
document.querySelector(':scope *');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
cssScope = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2015-02-16 17:14:37 +01:00
|
|
|
|
var 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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
var rawFilterFromTextarea = function() {
|
|
|
|
|
var s = taCandidate.value,
|
|
|
|
|
pos = s.indexOf('\n');
|
|
|
|
|
return pos === -1 ? s.trim() : s.slice(0, pos).trim();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2015-12-07 17:09:39 +01:00
|
|
|
|
var getElementBoundingClientRect = function(elem) {
|
2015-12-07 17:18:56 +01:00
|
|
|
|
var rect = typeof elem.getBoundingClientRect === 'function' ?
|
2015-12-07 17:09:39 +01:00
|
|
|
|
elem.getBoundingClientRect() :
|
|
|
|
|
{ height: 0, left: 0, top: 0, width: 0 };
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
2015-12-07 17:18:56 +01:00
|
|
|
|
var left = rect.left,
|
|
|
|
|
right = rect.right,
|
|
|
|
|
top = rect.top,
|
|
|
|
|
bottom = rect.bottom;
|
2015-12-07 17:09:39 +01:00
|
|
|
|
|
|
|
|
|
var children = elem.children,
|
|
|
|
|
i = children.length;
|
|
|
|
|
|
|
|
|
|
while ( i-- ) {
|
2015-12-07 17:18:56 +01:00
|
|
|
|
rect = getElementBoundingClientRect(children[i]);
|
|
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var 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
|
|
|
|
|
2015-02-09 14:03:29 +01:00
|
|
|
|
var ow = pickerRoot.contentWindow.innerWidth;
|
|
|
|
|
var oh = pickerRoot.contentWindow.innerHeight;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var ocean = [
|
|
|
|
|
'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'
|
|
|
|
|
];
|
|
|
|
|
var islands = [];
|
2014-11-07 12:59:01 +01:00
|
|
|
|
|
2014-11-03 15:28:55 +01:00
|
|
|
|
var elem, rect, poly;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
for ( var i = 0; i < elems.length; i++ ) {
|
2014-11-03 15:28:55 +01:00
|
|
|
|
elem = elems[i];
|
2015-02-10 18:59:27 +01:00
|
|
|
|
if ( elem === pickerRoot ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-12-07 17:09:39 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
result.push(diff[1].charAt(0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 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 = [];
|
|
|
|
|
for ( let s of elem.srcset.split(/\s*,\s+/) ) {
|
|
|
|
|
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;
|
2016-03-06 16:51:06 +01: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
|
|
|
|
|
vAPI.messaging.send(
|
|
|
|
|
'elementPicker',
|
|
|
|
|
{
|
|
|
|
|
what: 'elementPickerEprom',
|
|
|
|
|
lastNetFilterSession: lastNetFilterSession,
|
|
|
|
|
lastNetFilterHostname: lastNetFilterHostname,
|
|
|
|
|
lastNetFilterUnion: lastNetFilterUnion
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
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 netFilter2ndSources = {
|
2015-06-04 17:17:02 +02:00
|
|
|
|
'img': 'srcset'
|
|
|
|
|
};
|
|
|
|
|
|
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
|
|
|
|
|
2016-04-16 17:20:01 +02:00
|
|
|
|
var 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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2016-04-15 18:27:53 +02:00
|
|
|
|
var 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.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
var filterToDOMInterface = (function() {
|
|
|
|
|
// Net filters: we need to lookup manually -- translating into a foolproof
|
|
|
|
|
// CSS selector is just not possible.
|
|
|
|
|
var fromNetworkFilter = function(filter) {
|
|
|
|
|
var out = [];
|
|
|
|
|
// 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.
|
|
|
|
|
var reStr = '';
|
|
|
|
|
if ( filter.length > 1 && filter.charAt(0) === '/' && filter.slice(-1) === '/' ) {
|
|
|
|
|
reStr = filter.slice(1, -1);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
var rePrefix = '', reSuffix = '';
|
|
|
|
|
if ( filter.slice(0, 2) === '||' ) {
|
|
|
|
|
filter = filter.replace('||', '');
|
|
|
|
|
} else {
|
|
|
|
|
if ( filter.charAt(0) === '|' ) {
|
|
|
|
|
rePrefix = '^';
|
|
|
|
|
filter = filter.slice(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( filter.slice(-1) === '|' ) {
|
|
|
|
|
reSuffix = '$';
|
|
|
|
|
filter = filter.slice(0, -1);
|
|
|
|
|
}
|
|
|
|
|
reStr = rePrefix +
|
|
|
|
|
filter.replace(/[.+?${}()|[\]\\]/g, '\\$&').replace(/[\*^]+/g, '.*') +
|
|
|
|
|
reSuffix;
|
|
|
|
|
}
|
|
|
|
|
var reFilter = null;
|
2014-09-28 18:05:46 +02:00
|
|
|
|
try {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
reFilter = new RegExp(reStr);
|
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.
|
|
|
|
|
var src1stProps = netFilter1stSources;
|
|
|
|
|
var src2ndProps = netFilter2ndSources;
|
|
|
|
|
var srcProp, src;
|
|
|
|
|
var elems = document.querySelectorAll(Object.keys(src1stProps).join()),
|
|
|
|
|
iElem = elems.length,
|
|
|
|
|
elem;
|
|
|
|
|
while ( iElem-- ) {
|
|
|
|
|
elem = elems[iElem];
|
|
|
|
|
srcProp = src1stProps[elem.localName];
|
|
|
|
|
src = elem[srcProp];
|
|
|
|
|
if ( typeof src !== 'string' || src.length === 0 ) {
|
|
|
|
|
srcProp = src2ndProps[elem.localName];
|
|
|
|
|
src = elem[srcProp];
|
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
|
elems = candidateElements;
|
2016-04-15 18:27:53 +02:00
|
|
|
|
iElem = elems.length;
|
|
|
|
|
while ( iElem-- ) {
|
2016-10-01 18:34:25 +02:00
|
|
|
|
elem = elems[iElem];
|
|
|
|
|
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.
|
|
|
|
|
// TODO: This is still not working well for a[href], because there are many
|
|
|
|
|
// ways to compose a valid href to the same effective URL. One idea is to
|
|
|
|
|
// normalize all a[href] on the page, but for now I will wait and see, as I
|
|
|
|
|
// prefer to refrain from tampering with the page content if I can avoid it.
|
2016-12-25 22:56:39 +01:00
|
|
|
|
var fromPlainCosmeticFilter = function(filter) {
|
2018-11-06 19:11:03 +01:00
|
|
|
|
let elems;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
try {
|
2018-12-06 14:50:13 +01:00
|
|
|
|
elems = document.querySelectorAll(filter);
|
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; }
|
2018-11-06 19:20:44 +01:00
|
|
|
|
out.push({ type: 'cosmetic', elem });
|
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
|
|
|
|
|
// Handle procedural cosmetic filters.
|
2016-12-25 22:56:39 +01:00
|
|
|
|
var fromCompiledCosmeticFilter = function(raw) {
|
|
|
|
|
if ( typeof raw !== 'string' ) { return; }
|
|
|
|
|
var o;
|
|
|
|
|
try {
|
|
|
|
|
o = JSON.parse(raw);
|
|
|
|
|
} catch(ex) {
|
|
|
|
|
return;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
2016-12-25 22:56:39 +01:00
|
|
|
|
var elems;
|
|
|
|
|
if ( o.style ) {
|
2016-12-30 16:32:17 +01:00
|
|
|
|
elems = document.querySelectorAll(o.style[0]);
|
2017-11-21 19:38:31 +01:00
|
|
|
|
lastAction = o.style[0] + ' {' + o.style[1] + '}';
|
2016-12-30 16:32:17 +01:00
|
|
|
|
} else if ( o.tasks ) {
|
2016-12-25 22:56:39 +01:00
|
|
|
|
elems = vAPI.domFilterer.createProceduralFilter(o).exec();
|
2016-10-01 18:34:25 +02:00
|
|
|
|
}
|
2016-12-25 22:56:39 +01:00
|
|
|
|
if ( !elems ) { return; }
|
|
|
|
|
var out = [];
|
|
|
|
|
for ( var i = 0, n = elems.length; i < n; i++ ) {
|
|
|
|
|
out.push({ type: 'cosmetic', elem: elems[i] });
|
|
|
|
|
}
|
|
|
|
|
return out;
|
2016-10-01 18:34:25 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var lastFilter,
|
|
|
|
|
lastResultset,
|
|
|
|
|
lastAction,
|
|
|
|
|
appliedStyleTag,
|
|
|
|
|
applied = false,
|
|
|
|
|
previewing = false;
|
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
var queryAll = 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;
|
|
|
|
|
if ( filter.lastIndexOf('##', 0) === -1 ) {
|
|
|
|
|
lastResultset = fromNetworkFilter(filter);
|
|
|
|
|
if ( previewing ) { apply(); }
|
|
|
|
|
callback(lastResultset);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var selector = filter.slice(2);
|
|
|
|
|
lastResultset = fromPlainCosmeticFilter(selector);
|
|
|
|
|
if ( lastResultset ) {
|
|
|
|
|
if ( previewing ) { apply(); }
|
|
|
|
|
callback(lastResultset);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Procedural cosmetic filter
|
|
|
|
|
vAPI.messaging.send(
|
|
|
|
|
'elementPicker',
|
|
|
|
|
{ what: 'compileCosmeticFilterSelector', selector: selector },
|
|
|
|
|
function(response) {
|
|
|
|
|
lastResultset = fromCompiledCosmeticFilter(response);
|
|
|
|
|
if ( previewing ) { apply(); }
|
|
|
|
|
callback(lastResultset);
|
|
|
|
|
}
|
|
|
|
|
);
|
2016-10-01 18:34:25 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var applyHide = function() {
|
|
|
|
|
var htmlElem = document.documentElement,
|
|
|
|
|
items = lastResultset,
|
|
|
|
|
item, elem, style;
|
|
|
|
|
for ( var i = 0, n = items.length; i < n; i++ ) {
|
|
|
|
|
item = items[i];
|
|
|
|
|
elem = item.elem;
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1629
|
|
|
|
|
if ( elem === pickerRoot ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
style = elem.style;
|
|
|
|
|
if (
|
|
|
|
|
(elem !== htmlElem) &&
|
|
|
|
|
(item.type === 'cosmetic' || item.type === 'network' && item.src !== undefined)
|
|
|
|
|
) {
|
|
|
|
|
item.display = style.getPropertyValue('display');
|
|
|
|
|
item.displayPriority = style.getPropertyPriority('display');
|
|
|
|
|
style.setProperty('display', 'none', 'important');
|
|
|
|
|
}
|
|
|
|
|
if ( item.type === 'network' && item.style === 'background-image' ) {
|
|
|
|
|
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
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
var unapplyHide = function() {
|
|
|
|
|
var items = lastResultset, item;
|
|
|
|
|
for ( var i = 0, n = items.length; i < n; i++ ) {
|
|
|
|
|
item = items[i];
|
|
|
|
|
if ( item.hasOwnProperty('display') ) {
|
|
|
|
|
item.elem.style.setProperty(
|
|
|
|
|
'display',
|
|
|
|
|
item.display,
|
|
|
|
|
item.displayPriority
|
|
|
|
|
);
|
|
|
|
|
delete item.display;
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var unapplyStyle = function() {
|
|
|
|
|
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
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
var applyStyle = function() {
|
|
|
|
|
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
|
|
|
|
|
2016-10-01 18:34:25 +02:00
|
|
|
|
var apply = function() {
|
|
|
|
|
if ( applied ) {
|
|
|
|
|
unapply();
|
|
|
|
|
}
|
|
|
|
|
if ( lastResultset === undefined ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( typeof lastAction === 'string' ) {
|
|
|
|
|
applyStyle();
|
|
|
|
|
} else {
|
|
|
|
|
applyHide();
|
|
|
|
|
}
|
|
|
|
|
applied = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var unapply = function() {
|
|
|
|
|
if ( !applied ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( typeof lastAction === 'string' ) {
|
|
|
|
|
unapplyStyle();
|
|
|
|
|
} else {
|
|
|
|
|
unapplyHide();
|
|
|
|
|
}
|
|
|
|
|
applied = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var preview = function(filter) {
|
|
|
|
|
previewing = filter !== false;
|
|
|
|
|
if ( previewing ) {
|
2016-12-25 22:56:39 +01:00
|
|
|
|
queryAll(filter, function(items) {
|
|
|
|
|
if ( items !== undefined ) {
|
|
|
|
|
apply();
|
|
|
|
|
}
|
|
|
|
|
});
|
2016-10-01 18:34:25 +02:00
|
|
|
|
} else {
|
|
|
|
|
unapply();
|
|
|
|
|
}
|
|
|
|
|
pickerBody.classList.toggle('preview', previewing);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
previewing: function() { return previewing; },
|
|
|
|
|
preview: preview,
|
|
|
|
|
set: queryAll
|
|
|
|
|
};
|
|
|
|
|
})();
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
var userFilterFromCandidate = function(callback) {
|
|
|
|
|
var v = rawFilterFromTextarea();
|
|
|
|
|
filterToDOMInterface.set(v, function(items) {
|
|
|
|
|
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.
|
|
|
|
|
var hostname = window.location.hostname;
|
|
|
|
|
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?
|
|
|
|
|
if ( v.lastIndexOf('##', 0) === 0 ) {
|
|
|
|
|
callback(hostname + v);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
// Assume net filter
|
|
|
|
|
var 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
|
|
|
|
|
if ( v.lastIndexOf('||', 0) === -1 ) {
|
|
|
|
|
opts.push('domain=' + hostname);
|
|
|
|
|
}
|
2015-03-06 06:36:45 +01:00
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
var item = items[0];
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2016-12-25 22:56:39 +01:00
|
|
|
|
var onCandidateChanged = (function() {
|
|
|
|
|
var process = function(items) {
|
|
|
|
|
var elems = [], valid = items !== undefined;
|
|
|
|
|
if ( valid ) {
|
|
|
|
|
for ( var i = 0; i < items.length; i++ ) {
|
|
|
|
|
elems.push(items[i].elem);
|
|
|
|
|
}
|
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
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var 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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var filterChoiceFromEvent = function(ev) {
|
|
|
|
|
var li = ev.target;
|
|
|
|
|
var isNetFilter = li.textContent.slice(0, 2) !== '##';
|
|
|
|
|
var r = {
|
|
|
|
|
filters: isNetFilter ? netFilterCandidates : cosmeticFilterCandidates,
|
|
|
|
|
slot: 0,
|
|
|
|
|
modifier: ev.ctrlKey || ev.metaKey
|
|
|
|
|
};
|
|
|
|
|
while ( li.previousSibling !== null ) {
|
|
|
|
|
li = li.previousSibling;
|
|
|
|
|
r.slot += 1;
|
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var 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);
|
2016-12-25 22:56:39 +01:00
|
|
|
|
userFilterFromCandidate(function(filter) {
|
|
|
|
|
if ( !filter ) { return; }
|
2015-03-06 06:36:45 +01:00
|
|
|
|
var d = new Date();
|
2016-03-06 16:51:06 +01:00
|
|
|
|
vAPI.messaging.send(
|
|
|
|
|
'elementPicker',
|
|
|
|
|
{
|
|
|
|
|
what: 'createUserFilter',
|
|
|
|
|
filters: '! ' + d.toLocaleString() + ' ' + window.location.href + '\n' + filter,
|
2016-08-05 19:51:56 +02:00
|
|
|
|
pageDomain: window.location.hostname
|
2016-03-06 16:51:06 +01:00
|
|
|
|
}
|
|
|
|
|
);
|
2016-12-25 22:56:39 +01:00
|
|
|
|
filterToDOMInterface.preview(rawFilterFromTextarea());
|
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();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
var removeAllChildren = function(parent) {
|
|
|
|
|
while ( parent.firstChild ) {
|
|
|
|
|
parent.removeChild(parent.firstChild);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-07-13 17:44:36 +02:00
|
|
|
|
// TODO: for convenience I could provide a small set of net filters instead
|
|
|
|
|
// of just a single one. Truncating the right-most part of the path etc.
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var showDialog = function(options) {
|
|
|
|
|
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
|
|
|
|
|
var populate = function(src, des) {
|
2015-02-09 14:03:29 +01:00
|
|
|
|
var root = dialog.querySelector(des);
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var ul = root.querySelector('ul');
|
|
|
|
|
removeAllChildren(ul);
|
|
|
|
|
var li;
|
|
|
|
|
for ( var i = 0; i < src.length; i++ ) {
|
|
|
|
|
li = document.createElement('li');
|
|
|
|
|
li.textContent = src[i];
|
|
|
|
|
ul.appendChild(li);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
|
root.style.display = src.length !== 0 ? '' : 'none';
|
|
|
|
|
};
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var 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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2017-05-27 17:51:24 +02:00
|
|
|
|
var zap = function() {
|
|
|
|
|
if ( targetElements.length === 0 ) { return; }
|
|
|
|
|
var elem = targetElements[0],
|
|
|
|
|
style = window.getComputedStyle(elem);
|
|
|
|
|
// Heuristic to detect scroll-locking: remove such lock when detected.
|
|
|
|
|
if ( parseInt(style.zIndex, 10) >= 1000 || style.position === 'fixed' ) {
|
|
|
|
|
document.body.style.setProperty('overflow', 'auto', 'important');
|
|
|
|
|
document.documentElement.style.setProperty('overflow', 'auto', 'important');
|
2015-12-03 07:08:37 +01:00
|
|
|
|
}
|
2017-05-27 17:51:24 +02:00
|
|
|
|
elem.parentNode.removeChild(elem);
|
2017-05-28 19:45:11 +02:00
|
|
|
|
elem = elementFromPoint();
|
|
|
|
|
highlightElements(elem ? [elem] : []);
|
2014-09-28 20:38:17 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2017-05-27 17:51:24 +02:00
|
|
|
|
var elementFromPoint = (function() {
|
|
|
|
|
var lastX, lastY;
|
|
|
|
|
|
|
|
|
|
return function(x, y) {
|
|
|
|
|
if ( x !== undefined ) {
|
|
|
|
|
lastX = x; lastY = y;
|
|
|
|
|
} else if ( lastX !== undefined ) {
|
|
|
|
|
x = lastX; y = lastY;
|
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if ( !pickerRoot ) { return null; }
|
|
|
|
|
pickerRoot.style.pointerEvents = 'none';
|
|
|
|
|
var elem = document.elementFromPoint(x, y);
|
|
|
|
|
if ( elem === document.body || elem === document.documentElement ) {
|
|
|
|
|
elem = null;
|
|
|
|
|
}
|
|
|
|
|
pickerRoot.style.pointerEvents = '';
|
|
|
|
|
return elem;
|
|
|
|
|
};
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2015-05-27 21:15:20 +02:00
|
|
|
|
var onSvgHovered = (function() {
|
|
|
|
|
var timer = null;
|
2015-05-30 19:44:55 +02:00
|
|
|
|
var mx = 0, my = 0;
|
2015-05-27 21:15:20 +02:00
|
|
|
|
|
|
|
|
|
var onTimer = function() {
|
|
|
|
|
timer = null;
|
2015-05-30 19:44:55 +02:00
|
|
|
|
var 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;
|
|
|
|
|
if ( timer === null ) {
|
|
|
|
|
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
|
|
|
|
|
2017-05-29 16:38:22 +02:00
|
|
|
|
var onSvgTouchStartStop = (function() {
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-09-28 20:38:17 +02:00
|
|
|
|
var 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;
|
|
|
|
|
}
|
2017-10-28 15:07:41 +02:00
|
|
|
|
showDialog({ show: ev.type === 'touch' });
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2015-02-09 14:03:29 +01:00
|
|
|
|
var svgListening = function(on) {
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-07-13 17:38:52 +02:00
|
|
|
|
var onKeyPressed = function(ev) {
|
2017-05-27 17:51:24 +02:00
|
|
|
|
// Delete
|
2017-05-31 13:26:01 +02:00
|
|
|
|
if ( ev.key === 'Delete' && 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.
|
|
|
|
|
|
2014-09-28 20:38:17 +02:00
|
|
|
|
var onScrolled = function() {
|
2014-08-30 23:20:14 +02:00
|
|
|
|
highlightElements(targetElements, true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2015-03-09 15:11:22 +01:00
|
|
|
|
var pausePicker = function() {
|
2016-04-16 17:20:01 +02:00
|
|
|
|
pickerBody.classList.add('paused');
|
2015-03-09 15:11:22 +01:00
|
|
|
|
svgListening(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
var 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.
|
|
|
|
|
|
2014-07-13 17:38:52 +02:00
|
|
|
|
var 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);
|
|
|
|
|
pickerRoot.contentWindow.removeEventListener('keydown', onKeyPressed, true);
|
|
|
|
|
taCandidate.removeEventListener('input', onCandidateChanged);
|
|
|
|
|
dialog.removeEventListener('click', onDialogClicked);
|
|
|
|
|
svgListening(false);
|
2015-03-09 15:11:22 +01:00
|
|
|
|
svgRoot.removeEventListener('click', onSvgClicked);
|
2017-05-29 16:38:22 +02:00
|
|
|
|
svgRoot.removeEventListener('touchstart', onSvgTouchStartStop);
|
|
|
|
|
svgRoot.removeEventListener('touchend', onSvgTouchStartStop);
|
2015-02-09 14:03:29 +01:00
|
|
|
|
pickerRoot.parentNode.removeChild(pickerRoot);
|
2017-10-04 17:14:24 +02:00
|
|
|
|
pickerRoot.removeEventListener('load', stopPicker);
|
2015-02-09 14:03:29 +01:00
|
|
|
|
pickerRoot =
|
2016-04-16 17:20:01 +02:00
|
|
|
|
pickerBody =
|
2015-02-09 14:03:29 +01:00
|
|
|
|
dialog =
|
2015-03-09 15:11:22 +01:00
|
|
|
|
svgRoot = svgOcean = svgIslands =
|
2015-02-10 18:22:17 +01:00
|
|
|
|
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);
|
|
|
|
|
|
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
|
|
|
|
|
if ( details.clientX !== -1 ) {
|
2016-04-15 18:27:53 +02:00
|
|
|
|
if ( filtersFrom(details.clientX, details.clientY) !== 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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const bootstrapPicker = function() {
|
2017-10-04 17:14:24 +02:00
|
|
|
|
pickerRoot.removeEventListener('load', bootstrapPicker);
|
|
|
|
|
vAPI.shutdown.add(stopPicker);
|
|
|
|
|
vAPI.messaging.send(
|
|
|
|
|
'elementPicker',
|
|
|
|
|
{ what: 'elementPickerArguments' },
|
|
|
|
|
startPicker
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
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;
|
|
|
|
|
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const pickerCSS1 = [
|
2017-10-22 14:59:29 +02:00
|
|
|
|
'#' + pickerRoot.id + ' {',
|
|
|
|
|
pickerCSSStyle,
|
|
|
|
|
'}'
|
|
|
|
|
].join('\n');
|
2018-12-20 23:29:39 +01:00
|
|
|
|
const pickerCSS2 = [
|
2017-10-22 14:59:29 +02:00
|
|
|
|
'[' + pickerRoot.id + '-clickblind] {',
|
|
|
|
|
'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
|
|
|
|
|
2017-10-04 17:14:24 +02:00
|
|
|
|
pickerRoot.addEventListener('load', bootstrapPicker);
|
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;
|