2014-07-13 02:32:44 +02:00
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
|
|
µBlock - a Chromium browser extension to block requests.
|
|
|
|
|
Copyright (C) 2014 Raymond Hill
|
|
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
|
|
|
|
|
|
Home: https://github.com/gorhill/uBlock
|
|
|
|
|
*/
|
|
|
|
|
|
2014-10-20 13:26:02 +02:00
|
|
|
|
/* global CSS */
|
2014-10-19 13:11:27 +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, […]
|
|
|
|
|
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
|
|
|
|
|
// 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), […]
|
|
|
|
|
(
|
|
|
|
|
index == 1 &&
|
|
|
|
|
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
|
|
|
|
|
firstCodeUnit == 0x002D
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
// 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 ||
|
|
|
|
|
codeUnit == 0x002D ||
|
|
|
|
|
codeUnit == 0x005F ||
|
|
|
|
|
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() {
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-11-04 12:32:44 +01:00
|
|
|
|
// don't run in frames
|
|
|
|
|
if (window.top !== window) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-13 15:33:43 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/314#issuecomment-58878112
|
|
|
|
|
// Using an id makes uBlock's CSS rules more specific, thus prevents
|
|
|
|
|
// surrounding external rules from winning over own rules.
|
|
|
|
|
var µBlockId = CSS.escape('µBlock');
|
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
var pickerRoot = document.getElementById(µBlockId);
|
|
|
|
|
|
|
|
|
|
if ( pickerRoot ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var localMessager = vAPI.messaging.channel('element-picker.js');
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var svgns = 'http://www.w3.org/2000/svg';
|
|
|
|
|
|
|
|
|
|
var svgRoot = null;
|
|
|
|
|
var svgOcean = null;
|
|
|
|
|
var svgIslands = null;
|
|
|
|
|
var divDialog = null;
|
|
|
|
|
var taCandidate = null;
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var urlNormalizer = null;
|
|
|
|
|
|
|
|
|
|
var netFilterCandidates = [];
|
|
|
|
|
var cosmeticFilterCandidates = [];
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
var targetElements = [];
|
2014-08-30 23:20:14 +02:00
|
|
|
|
var svgWidth = 0;
|
|
|
|
|
var svgHeight = 0;
|
2014-10-23 14:12:37 +02:00
|
|
|
|
var elementFromPointCSSProperty = 'pointerEvents';
|
|
|
|
|
var onSvgHoveredTimer = null;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
var pickerPaused = function() {
|
2014-10-23 14:12:37 +02:00
|
|
|
|
return pickerRoot.classList.contains('paused');
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
var pausePicker = function() {
|
2014-10-23 14:12:37 +02:00
|
|
|
|
pickerRoot.classList.add('paused');
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
var unpausePicker = function() {
|
2014-10-23 14:12:37 +02:00
|
|
|
|
pickerRoot.classList.remove('paused');
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
var pickerRootDistance = function(elem) {
|
|
|
|
|
var distance = 0;
|
|
|
|
|
while ( elem ) {
|
|
|
|
|
if ( elem === pickerRoot ) {
|
|
|
|
|
return distance;
|
|
|
|
|
}
|
|
|
|
|
elem = elem.parentNode;
|
|
|
|
|
distance += 1;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2014-10-20 13:26:02 +02:00
|
|
|
|
var ow = parseInt(svgRoot.style.width, 10);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var ocean = [
|
|
|
|
|
'M0 0',
|
|
|
|
|
'h', ow,
|
2014-10-20 13:26:02 +02:00
|
|
|
|
'v', parseInt(svgRoot.style.height, 10),
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'h-', ow,
|
|
|
|
|
'z'
|
|
|
|
|
];
|
2014-10-11 22:43:17 +02:00
|
|
|
|
var offx = window.pageXOffset;
|
|
|
|
|
var offy = window.pageYOffset;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var islands = [];
|
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];
|
|
|
|
|
if ( typeof elem.getBoundingClientRect !== 'function' ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
rect = elem.getBoundingClientRect();
|
|
|
|
|
poly = 'M' + (rect.left + offx) + ' ' + (rect.top + offy) +
|
|
|
|
|
'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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
var removeElements = function(elems) {
|
|
|
|
|
var i = elems.length, elem;
|
|
|
|
|
while ( i-- ) {
|
|
|
|
|
elem = elems[i];
|
|
|
|
|
if ( elem.parentNode ) {
|
|
|
|
|
elem.parentNode.removeChild(elem);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
// Extract the best possible net filter, i.e. as specific as possible.
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var netFilterFromElement = function(elem, out) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
if ( elem === null ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( elem.nodeType !== 1 ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var tagName = elem.tagName.toLowerCase();
|
|
|
|
|
if ( tagName !== 'img' && tagName !== 'iframe' ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-09-26 00:10:58 +02:00
|
|
|
|
var src = elem.getAttribute('src');
|
|
|
|
|
if ( typeof src !== 'string' || src.length === 0 ) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
|
// Remove fragment
|
|
|
|
|
var pos = src.indexOf('#');
|
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
|
src = src.slice(0, pos);
|
|
|
|
|
}
|
|
|
|
|
// Feed the attribute to a link element, then retrieve back: this
|
|
|
|
|
// should normalize it.
|
|
|
|
|
urlNormalizer.href = src;
|
|
|
|
|
src = urlNormalizer.href;
|
|
|
|
|
// Anchor absolute filter to hostname
|
|
|
|
|
src = src.replace(/^https?:\/\//, '||');
|
|
|
|
|
out.push(src);
|
|
|
|
|
// Suggest a less narrow filter if possible
|
|
|
|
|
pos = src.indexOf('?');
|
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
|
src = src.slice(0, pos);
|
|
|
|
|
out.push(src);
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
// Extract the best possible cosmetic filter, i.e. as specific as possible.
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var cosmeticFilterFromElement = function(elem, out) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
if ( elem === null ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ( elem.nodeType !== 1 ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var tagName = elem.tagName.toLowerCase();
|
2014-07-14 08:14:13 +02:00
|
|
|
|
var prefix = '';
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var suffix = [];
|
|
|
|
|
var v;
|
|
|
|
|
|
|
|
|
|
// Id
|
|
|
|
|
v = typeof elem.id === 'string' && CSS.escape(elem.id);
|
|
|
|
|
if ( v ) {
|
|
|
|
|
suffix.push('#', v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Class(es)
|
|
|
|
|
v = typeof elem.className === 'string' && elem.className.trim();
|
|
|
|
|
if ( v.length ) {
|
|
|
|
|
v = v.split(/\s+/);
|
|
|
|
|
var i = v.length;
|
|
|
|
|
while ( i-- ) {
|
|
|
|
|
v[i] = CSS.escape(v[i]);
|
|
|
|
|
}
|
|
|
|
|
suffix.push('.', v.join('.'));
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-14 08:14:13 +02:00
|
|
|
|
if ( suffix.length === 0 ) {
|
|
|
|
|
prefix = tagName;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
// Attributes (depends on tag name)
|
|
|
|
|
var attributes = [], attr;
|
|
|
|
|
switch ( tagName ) {
|
|
|
|
|
case 'a':
|
|
|
|
|
v = elem.getAttribute('href');
|
|
|
|
|
if ( v ) {
|
|
|
|
|
v = v.replace(/\?.*$/, '');
|
2014-10-13 15:47:18 +02:00
|
|
|
|
if ( v.length ) {
|
|
|
|
|
attributes.push({ k: 'href', v: v });
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'img':
|
|
|
|
|
v = elem.getAttribute('alt');
|
|
|
|
|
if ( v && v.length !== 0 ) {
|
|
|
|
|
attributes.push({ k: 'alt', v: v });
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
while ( attr = attributes.pop() ) {
|
|
|
|
|
if ( attr.v.length === 0 ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
v = elem.getAttribute(attr.k);
|
|
|
|
|
if ( attr.v === v ) {
|
|
|
|
|
suffix.push('[', attr.k, '="', attr.v, '"]');
|
|
|
|
|
} else if ( v.indexOf(attr.v) === 0 ) {
|
|
|
|
|
suffix.push('[', attr.k, '^="', attr.v, '"]');
|
|
|
|
|
} else {
|
|
|
|
|
suffix.push('[', attr.k, '*="', attr.v, '"]');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
out.push('##' + prefix + suffix.join(''));
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var filtersFromElement = function(elem) {
|
|
|
|
|
netFilterCandidates.length = 0;
|
|
|
|
|
cosmeticFilterCandidates.length = 0;
|
|
|
|
|
while ( elem && elem !== document.body ) {
|
|
|
|
|
netFilterFromElement(elem, netFilterCandidates);
|
|
|
|
|
cosmeticFilterFromElement(elem, cosmeticFilterCandidates);
|
|
|
|
|
elem = elem.parentNode;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
var elementsFromFilter = function(filter) {
|
|
|
|
|
var out = [];
|
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
if ( filter.slice(0, 2) === '##' ) {
|
|
|
|
|
try {
|
|
|
|
|
out = document.querySelectorAll(filter.replace('##', ''));
|
|
|
|
|
}
|
|
|
|
|
catch (e) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
|
return out;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
|
|
|
|
|
|
// Net filters: we need to lookup manually -- translating into a
|
|
|
|
|
// foolproof CSS selector is just not possible
|
|
|
|
|
if ( filter.slice(0, 2) === '||' ) {
|
|
|
|
|
filter = filter.replace('||', '');
|
|
|
|
|
}
|
|
|
|
|
var elems = document.querySelectorAll('[src]');
|
|
|
|
|
var i = elems.length;
|
|
|
|
|
var elem;
|
|
|
|
|
while ( i-- ) {
|
|
|
|
|
elem = elems[i];
|
|
|
|
|
if ( typeof elem.src !== 'string' ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ( elem.src.indexOf(filter) !== -1 ) {
|
|
|
|
|
out.push(elem);
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
|
return out;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
// https://www.youtube.com/watch?v=YI2XuIOW3gM
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
var userFilterFromCandidate = function() {
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var v = taCandidate.value;
|
|
|
|
|
|
|
|
|
|
var elems = elementsFromFilter(v);
|
|
|
|
|
if ( elems.length === 0 ) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cosmetic filter?
|
2014-09-26 00:10:58 +02:00
|
|
|
|
if ( v.slice(0, 2) === '##' ) {
|
|
|
|
|
return window.location.hostname + v;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
2014-09-26 00:10:58 +02:00
|
|
|
|
// If domain included in filter, no need for domain option
|
|
|
|
|
if ( v.slice(0, 2) === '||' ) {
|
|
|
|
|
return v;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2014-09-26 00:10:58 +02:00
|
|
|
|
// Assume net filter
|
|
|
|
|
return v + '$domain=' + window.location.hostname;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var onCandidateChanged = function() {
|
|
|
|
|
var elems = elementsFromFilter(taCandidate.value);
|
|
|
|
|
divDialog.querySelector('#create').disabled = elems.length === 0;
|
|
|
|
|
highlightElements(elems);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
var candidateFromFilterChoice = function(filterChoice) {
|
|
|
|
|
var slot = filterChoice.slot;
|
|
|
|
|
var filters = filterChoice.filters;
|
|
|
|
|
var filter = filters[slot];
|
2014-07-13 17:38:52 +02:00
|
|
|
|
|
2014-09-28 18:05:46 +02:00
|
|
|
|
if ( filter === undefined ) {
|
|
|
|
|
return '';
|
2014-07-13 17:38:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For net filters there no such thing as a path
|
2014-09-28 18:05:46 +02:00
|
|
|
|
if ( filterChoice.type === 'net' || filterChoice.modifier ) {
|
|
|
|
|
return filter;
|
2014-07-13 17:38:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return path: the target element, then all siblings prepended
|
|
|
|
|
var selector = [];
|
2014-09-28 18:05:46 +02:00
|
|
|
|
for ( ; slot < filters.length; slot++ ) {
|
|
|
|
|
filter = filters[slot];
|
|
|
|
|
selector.unshift(filter.replace(/^##/, ''));
|
2014-09-26 00:10:58 +02:00
|
|
|
|
// Stop at any element with an id: these are unique in a web page
|
2014-09-28 18:05:46 +02:00
|
|
|
|
if ( filter.slice(0, 3) === '###' ) {
|
2014-09-26 00:10:58 +02:00
|
|
|
|
break;
|
|
|
|
|
}
|
2014-07-13 17:38:52 +02:00
|
|
|
|
}
|
|
|
|
|
return '##' + selector.join(' > ');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
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 = {
|
|
|
|
|
type: isNetFilter ? 'net' : 'cosmetic',
|
|
|
|
|
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) {
|
|
|
|
|
if ( ev.target === null ) {
|
|
|
|
|
/* do nothing */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( ev.target.id === 'create' ) {
|
|
|
|
|
var filter = userFilterFromCandidate();
|
|
|
|
|
if ( filter ) {
|
2014-10-20 13:26:02 +02:00
|
|
|
|
localMessager.send({ what: 'createUserFilter', filters: filter });
|
2014-09-28 18:05:46 +02:00
|
|
|
|
removeElements(elementsFromFilter(taCandidate.value));
|
2014-07-13 02:32:44 +02:00
|
|
|
|
stopPicker();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( ev.target.id === 'pick' ) {
|
|
|
|
|
unpausePicker();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( ev.target.id === 'quit' ) {
|
|
|
|
|
stopPicker();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if ( ev.target.tagName.toLowerCase() === 'li' && pickerRootDistance(ev.target) === 5 ) {
|
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 || {};
|
|
|
|
|
|
|
|
|
|
// Create lists of candidate filters
|
|
|
|
|
var populate = function(src, des) {
|
|
|
|
|
var root = divDialog.querySelector(des);
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
divDialog.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none';
|
|
|
|
|
divDialog.querySelector('#create').disabled = true;
|
|
|
|
|
|
|
|
|
|
// Auto-select a candidate filter
|
|
|
|
|
var filterChoice = {
|
|
|
|
|
type: '',
|
|
|
|
|
filters: [],
|
|
|
|
|
slot: 0,
|
|
|
|
|
modifier: options.modifier || false
|
|
|
|
|
};
|
|
|
|
|
if ( netFilterCandidates.length ) {
|
|
|
|
|
filterChoice.type = 'net';
|
|
|
|
|
filterChoice.filters = netFilterCandidates;
|
|
|
|
|
} else if ( cosmeticFilterCandidates.length ) {
|
|
|
|
|
filterChoice.type = 'cosmetic';
|
|
|
|
|
filterChoice.filters = cosmeticFilterCandidates;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
taCandidate.value = '';
|
2014-09-28 18:05:46 +02:00
|
|
|
|
if ( filterChoice.type !== '' ) {
|
|
|
|
|
taCandidate.value = candidateFromFilterChoice(filterChoice);
|
|
|
|
|
onCandidateChanged();
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-09-28 20:38:17 +02:00
|
|
|
|
var elementFromPoint = function(x, y) {
|
2014-10-23 14:12:37 +02:00
|
|
|
|
svgRoot.style[elementFromPointCSSProperty] = 'none';
|
2014-09-28 20:38:17 +02:00
|
|
|
|
var elem = document.elementFromPoint(x, y);
|
|
|
|
|
if ( elem === document.body || elem === document.documentElement ) {
|
|
|
|
|
elem = null;
|
|
|
|
|
}
|
2014-10-23 14:12:37 +02:00
|
|
|
|
svgRoot.style[elementFromPointCSSProperty] = '';
|
2014-09-28 20:38:17 +02:00
|
|
|
|
return elem;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
var onSvgHovered = function(ev) {
|
2014-10-23 14:12:37 +02:00
|
|
|
|
if ( pickerPaused() || onSvgHoveredTimer) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2014-10-23 14:12:37 +02:00
|
|
|
|
|
|
|
|
|
onSvgHoveredTimer = setTimeout(function() {
|
|
|
|
|
var elem = elementFromPoint(ev.clientX, ev.clientY);
|
|
|
|
|
highlightElements(elem ? [elem] : []);
|
|
|
|
|
onSvgHoveredTimer = null;
|
|
|
|
|
}, 50);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-09-28 20:38:17 +02:00
|
|
|
|
var onSvgClicked = function(ev) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
if ( pickerPaused() ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-09-28 20:38:17 +02:00
|
|
|
|
var elem = elementFromPoint(ev.clientX, ev.clientY);
|
|
|
|
|
if ( elem === null ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
filtersFromElement(elem);
|
2014-09-28 18:05:46 +02:00
|
|
|
|
showDialog();
|
2014-07-13 02:32:44 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-07-13 17:38:52 +02:00
|
|
|
|
var onKeyPressed = function(ev) {
|
|
|
|
|
if ( ev.key === 27 || ev.keyCode === 27 ) {
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
stopPicker();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-08-30 23:22:31 +02:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/190
|
|
|
|
|
// 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
|
|
|
|
var newHeight = this.scrollY + this.innerHeight;
|
|
|
|
|
if ( newHeight > svgHeight ) {
|
|
|
|
|
svgHeight = newHeight;
|
2014-10-20 13:26:02 +02:00
|
|
|
|
svgRoot.style.height = svgHeight + 'px';
|
|
|
|
|
svgRoot.setAttribute('viewBox', '0 0 ' + svgWidth + ' ' + svgHeight);
|
2014-08-30 23:20:14 +02:00
|
|
|
|
}
|
|
|
|
|
highlightElements(targetElements, 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() {
|
|
|
|
|
if ( pickerRoot !== null ) {
|
2014-10-20 13:26:02 +02:00
|
|
|
|
window.removeEventListener('keydown', onKeyPressed, true);
|
|
|
|
|
window.removeEventListener('scroll', onScrolled, true);
|
2014-07-13 17:38:52 +02:00
|
|
|
|
taCandidate.removeEventListener('input', onCandidateChanged);
|
|
|
|
|
divDialog.removeEventListener('click', onDialogClicked);
|
|
|
|
|
svgRoot.removeEventListener('mousemove', onSvgHovered);
|
|
|
|
|
svgRoot.removeEventListener('click', onSvgClicked);
|
2014-10-20 13:26:02 +02:00
|
|
|
|
pickerRoot.parentNode.removeChild(pickerRoot);
|
2014-10-17 21:44:19 +02:00
|
|
|
|
pickerRoot =
|
|
|
|
|
divDialog =
|
|
|
|
|
svgRoot = svgOcean = svgIslands =
|
|
|
|
|
taCandidate =
|
2014-09-28 18:05:46 +02:00
|
|
|
|
urlNormalizer = null;
|
2014-10-20 13:26:02 +02:00
|
|
|
|
localMessager.close();
|
2014-07-13 17:38:52 +02:00
|
|
|
|
}
|
|
|
|
|
targetElements = [];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
var startPicker = function(details) {
|
2014-07-13 02:32:44 +02:00
|
|
|
|
pickerRoot = document.createElement('div');
|
2014-10-13 15:33:43 +02:00
|
|
|
|
pickerRoot.id = µBlockId;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
|
|
|
|
var pickerStyle = document.createElement('style');
|
|
|
|
|
pickerStyle.setAttribute('scoped', '');
|
|
|
|
|
pickerStyle.textContent = [
|
2014-10-19 19:36:52 +02:00
|
|
|
|
'#µBlock, #µBlock * {',
|
2014-09-05 19:16:18 +02:00
|
|
|
|
'background: transparent;',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'background-image: none;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'border: 0;',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'border-radius: 0;',
|
|
|
|
|
'box-shadow: none;',
|
2014-09-05 19:16:18 +02:00
|
|
|
|
'color: #000;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'font: 12px sans-serif;',
|
2014-10-19 16:50:19 +02:00
|
|
|
|
'height: auto;',
|
2014-10-11 18:22:46 +02:00
|
|
|
|
'letter-spacing: normal;',
|
2014-09-05 19:16:18 +02:00
|
|
|
|
'margin: 0;',
|
2014-09-28 18:05:46 +02:00
|
|
|
|
'max-width: none;',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'min-height: 0;',
|
2014-09-05 19:16:18 +02:00
|
|
|
|
'outline: 0;',
|
|
|
|
|
'overflow: visible;',
|
|
|
|
|
'padding: 0;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'text-transform: none;',
|
|
|
|
|
'vertical-align: baseline;',
|
2014-09-29 14:38:02 +02:00
|
|
|
|
'z-index: auto;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock {',
|
|
|
|
|
'position: absolute;',
|
|
|
|
|
'top: 0;',
|
|
|
|
|
'left: 0;',
|
|
|
|
|
'}',
|
|
|
|
|
'#µBlock li {',
|
2014-10-11 18:22:46 +02:00
|
|
|
|
'display: block;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock button {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'border: 1px solid #aaa;',
|
|
|
|
|
'padding: 6px 8px 4px 8px;',
|
|
|
|
|
'box-sizing: border-box;',
|
|
|
|
|
'box-shadow: none;',
|
|
|
|
|
'border-radius: 3px;',
|
2014-07-17 16:50:53 +02:00
|
|
|
|
'display: inline;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'line-height: 1;',
|
|
|
|
|
'color: #444;',
|
|
|
|
|
'background-color: #ccc;',
|
|
|
|
|
'cursor: pointer;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock button:hover {',
|
|
|
|
|
'background: none;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'background-color: #eee;',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'background-image: none;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock button:disabled {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'color: #999;',
|
|
|
|
|
'background-color: #ccc;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock button#create:not(:disabled) {',
|
2014-09-28 18:05:46 +02:00
|
|
|
|
'background-color: #ffdca8;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > svg {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'position: absolute;',
|
|
|
|
|
'top: 0;',
|
|
|
|
|
'left: 0;',
|
2014-10-23 14:12:37 +02:00
|
|
|
|
'pointer-events: auto;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'cursor: crosshair;',
|
|
|
|
|
'z-index: 4999999999;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock.paused > svg {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'cursor: wait;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > svg > path:first-child {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'fill: rgba(0,0,0,0.75);',
|
|
|
|
|
'fill-rule: evenodd;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > svg > path + path {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'stroke: #F00;',
|
|
|
|
|
'stroke-width: 0.5px;',
|
|
|
|
|
'fill: rgba(255,0,0,0.25);',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'padding: 4px;',
|
|
|
|
|
'display: none;',
|
|
|
|
|
'position: fixed;',
|
|
|
|
|
'right: 4px;',
|
|
|
|
|
'bottom: 4px;',
|
|
|
|
|
'width: 30em;',
|
|
|
|
|
'font: 12px sans-serif;',
|
|
|
|
|
'background-color: rgba(255,255,255,0.9);',
|
|
|
|
|
'z-index: 5999999999;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock.paused > div {',
|
2014-10-11 22:43:17 +02:00
|
|
|
|
'opacity: 0.2;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'display: initial;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock.paused > div:hover {',
|
2014-10-11 22:43:17 +02:00
|
|
|
|
'opacity: 1;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > div {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'padding: 0;',
|
|
|
|
|
'box-sizing: border-box;',
|
|
|
|
|
'width: 100%;',
|
2014-07-13 17:38:52 +02:00
|
|
|
|
'height: 8em;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'position: relative;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > div > textarea {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'border: 1px solid #ccc;',
|
|
|
|
|
'padding: 2px;',
|
|
|
|
|
'box-sizing: border-box;',
|
2014-10-19 16:50:19 +02:00
|
|
|
|
'width: 100% !important;',
|
|
|
|
|
'height: 100% !important;',
|
|
|
|
|
'overflow: hidden !important;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'resize: none;',
|
|
|
|
|
'background-color: white;',
|
|
|
|
|
'font: 11px monospace;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > div > div {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'position: absolute;',
|
|
|
|
|
'right: 2px;',
|
|
|
|
|
'bottom: 2px;',
|
|
|
|
|
'opacity: 0.2;',
|
2014-10-04 20:17:17 +02:00
|
|
|
|
'direction: ltr;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > div > div:hover {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'opacity: 1;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > div > div > button {',
|
|
|
|
|
'margin-left: 3px !important;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > ul {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'margin: 0;',
|
|
|
|
|
'border: 1px solid #ccc;',
|
|
|
|
|
'padding: 3px;',
|
|
|
|
|
'list-style-type: none;',
|
|
|
|
|
'text-align: left;',
|
|
|
|
|
'overflow: hidden;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > ul > li {',
|
2014-10-04 20:17:17 +02:00
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > ul > li:not(:first-child) {',
|
2014-09-28 18:05:46 +02:00
|
|
|
|
'margin-top: 0.5em;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > ul > li > span:nth-of-type(1) {',
|
2014-09-28 18:05:46 +02:00
|
|
|
|
'font-weight: bold;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > ul > li > span:nth-of-type(2) {',
|
2014-09-28 18:05:46 +02:00
|
|
|
|
'font-size: smaller;',
|
|
|
|
|
'color: gray;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > ul > li > ul {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'margin: 0 0 0 1em;',
|
|
|
|
|
'list-style-type: none;',
|
|
|
|
|
'text-align: left;',
|
|
|
|
|
'background-color: #eee;',
|
|
|
|
|
'overflow: hidden;',
|
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > ul > li > ul > li {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'font: 11px monospace;',
|
|
|
|
|
'white-space: nowrap;',
|
|
|
|
|
'cursor: pointer;',
|
2014-10-04 20:17:17 +02:00
|
|
|
|
'direction: ltr;',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'}',
|
2014-10-13 15:33:43 +02:00
|
|
|
|
'#µBlock > div > ul > li > ul > li:hover {',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'background-color: rgba(255,255,255,1.0);',
|
|
|
|
|
'}',
|
|
|
|
|
''
|
|
|
|
|
].join('\n');
|
|
|
|
|
pickerRoot.appendChild(pickerStyle);
|
|
|
|
|
|
|
|
|
|
svgRoot = document.createElementNS(svgns, 'svg');
|
2014-10-20 13:26:02 +02:00
|
|
|
|
svgRoot.appendChild(document.createElementNS(svgns, 'path'));
|
|
|
|
|
svgRoot.appendChild(document.createElementNS(svgns, 'path'));
|
2014-08-30 23:20:14 +02:00
|
|
|
|
svgWidth = document.documentElement.scrollWidth;
|
|
|
|
|
svgHeight = Math.max(
|
|
|
|
|
document.documentElement.scrollHeight,
|
|
|
|
|
window.scrollY + window.innerHeight
|
|
|
|
|
);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
svgRoot.setAttribute('x', 0);
|
|
|
|
|
svgRoot.setAttribute('y', 0);
|
2014-10-20 13:26:02 +02:00
|
|
|
|
svgRoot.style.width = svgWidth + 'px';
|
|
|
|
|
svgRoot.style.height = svgHeight + 'px';
|
|
|
|
|
svgRoot.setAttribute('viewBox', '0 0 ' + svgWidth + ' ' + svgHeight);
|
|
|
|
|
svgOcean = svgRoot.firstChild;
|
|
|
|
|
svgIslands = svgRoot.lastChild;
|
2014-07-13 02:32:44 +02:00
|
|
|
|
pickerRoot.appendChild(svgRoot);
|
|
|
|
|
|
2014-07-13 04:53:17 +02:00
|
|
|
|
// TODO: do not rely on element ids, they could collide with whatever
|
|
|
|
|
// is used in the page. Just use built-in hierarchy of elements as
|
|
|
|
|
// selectors.
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
divDialog = document.createElement('div');
|
|
|
|
|
divDialog.innerHTML = [
|
|
|
|
|
'<div>',
|
2014-10-04 20:17:17 +02:00
|
|
|
|
'<textarea dir="ltr" spellcheck="false"></textarea>',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'<div>',
|
|
|
|
|
'<button id="create" type="button" disabled>.</button>',
|
|
|
|
|
'<button id="pick" type="button">.</button>',
|
|
|
|
|
'<button id="quit" type="button">.</button>',
|
|
|
|
|
'</div>',
|
|
|
|
|
'</div>',
|
|
|
|
|
'<ul>',
|
2014-09-28 22:11:32 +02:00
|
|
|
|
'<li id="netFilters"><span>.</span><ul></ul>',
|
2014-10-04 20:17:17 +02:00
|
|
|
|
'<li id="cosmeticFilters"><span>.</span> <span>.</span><ul></ul>',
|
2014-07-13 02:32:44 +02:00
|
|
|
|
'</ul>',
|
|
|
|
|
''
|
|
|
|
|
].join('');
|
|
|
|
|
pickerRoot.appendChild(divDialog);
|
|
|
|
|
|
2014-11-04 12:32:44 +01:00
|
|
|
|
// https://github.com/gorhill/uBlock/issues/344#issuecomment-60775958
|
2014-11-03 15:28:55 +01:00
|
|
|
|
// Insert in `html` tag, not `body` tag.
|
|
|
|
|
document.documentElement.appendChild(pickerRoot);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
svgRoot.addEventListener('click', onSvgClicked);
|
|
|
|
|
svgRoot.addEventListener('mousemove', onSvgHovered);
|
|
|
|
|
divDialog.addEventListener('click', onDialogClicked);
|
|
|
|
|
taCandidate = divDialog.querySelector('textarea');
|
|
|
|
|
taCandidate.addEventListener('input', onCandidateChanged);
|
2014-09-28 18:05:46 +02:00
|
|
|
|
urlNormalizer = document.createElement('a');
|
2014-10-20 13:26:02 +02:00
|
|
|
|
window.addEventListener('scroll', onScrolled, true);
|
|
|
|
|
window.addEventListener('keydown', onKeyPressed, true);
|
2014-09-28 20:58:26 +02:00
|
|
|
|
|
|
|
|
|
highlightElements([], true);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
var i18nMap = {
|
|
|
|
|
'#µBlock > div': '@@bidi_dir',
|
|
|
|
|
'#create': 'create',
|
|
|
|
|
'#pick': 'pick',
|
|
|
|
|
'#quit': 'quit',
|
|
|
|
|
'ul > li#netFilters > span:nth-of-type(1)': 'netFilters',
|
|
|
|
|
'ul > li#cosmeticFilters > span:nth-of-type(1)': 'cosmeticFilters',
|
|
|
|
|
'ul > li#cosmeticFilters > span:nth-of-type(2)': 'cosmeticFiltersHint'
|
|
|
|
|
};
|
2014-10-19 19:36:52 +02:00
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
if (details.i18n['@@bidi_dir']) {
|
|
|
|
|
divDialog.style.direction = details.i18n['@@bidi_dir'];
|
|
|
|
|
delete i18nMap['#µBlock > div'];
|
|
|
|
|
}
|
2014-10-19 19:36:52 +02:00
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
for ( var k in i18nMap ) {
|
|
|
|
|
if ( i18nMap.hasOwnProperty(k) === false ) {
|
|
|
|
|
continue;
|
2014-10-13 15:38:36 +02:00
|
|
|
|
}
|
2014-10-23 14:12:37 +02:00
|
|
|
|
divDialog.querySelector(k).firstChild.nodeValue = details.i18n[i18nMap[k]];
|
|
|
|
|
}
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
// First we test if pointer-events are hadnled in Node.elementFromPoint().
|
|
|
|
|
// If the browser ignores pointer-events in Node.elementFromPoint(),
|
|
|
|
|
// then use the display property instead (e.g., for older Safari).
|
|
|
|
|
var elem = elementFromPoint(0, 0);
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
if ( elem === svgRoot ) {
|
|
|
|
|
elementFromPointCSSProperty = 'display';
|
|
|
|
|
}
|
2014-09-28 18:05:46 +02:00
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
// Auto-select a specific target, if any, and if possible
|
|
|
|
|
|
|
|
|
|
// Try using mouse position
|
|
|
|
|
if ( details.clientX !== -1 ) {
|
|
|
|
|
elem = elementFromPoint(details.clientX, details.clientY);
|
|
|
|
|
if ( elem !== null ) {
|
2014-10-13 15:38:36 +02:00
|
|
|
|
filtersFromElement(elem);
|
2014-10-23 14:12:37 +02:00
|
|
|
|
showDialog();
|
2014-10-13 15:38:36 +02:00
|
|
|
|
return;
|
2014-09-28 18:05:46 +02:00
|
|
|
|
}
|
2014-10-23 14:12:37 +02:00
|
|
|
|
}
|
2014-10-13 15:38:36 +02:00
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
// No mouse position available, use suggested target
|
|
|
|
|
var target = details.target || '';
|
|
|
|
|
var pos = target.indexOf('\t');
|
|
|
|
|
if ( pos === -1 ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var srcAttrMap = {
|
|
|
|
|
'a': 'href',
|
|
|
|
|
'img': 'src',
|
|
|
|
|
'iframe': 'src',
|
|
|
|
|
'video': 'src',
|
|
|
|
|
'audio': 'src'
|
|
|
|
|
};
|
|
|
|
|
var tagName = target.slice(0, pos);
|
|
|
|
|
var url = target.slice(pos + 1);
|
|
|
|
|
var attr = srcAttrMap[tagName];
|
|
|
|
|
if ( attr === undefined ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var elems = document.querySelectorAll(tagName + '[' + attr + ']');
|
|
|
|
|
var i = elems.length;
|
|
|
|
|
var src;
|
|
|
|
|
while ( i-- ) {
|
|
|
|
|
elem = elems[i];
|
|
|
|
|
src = elem[attr];
|
|
|
|
|
if ( typeof src !== 'string' || src === '' ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ( src !== url ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
filtersFromElement(elem);
|
|
|
|
|
showDialog({ modifier: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-10-13 15:38:36 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
2014-10-23 14:12:37 +02:00
|
|
|
|
localMessager.send({ what: 'elementPickerArguments' }, startPicker);
|
2014-10-13 15:38:36 +02:00
|
|
|
|
|
2014-10-20 13:26:02 +02:00
|
|
|
|
// This triggers the hiding of the popover in Safari
|
|
|
|
|
window.focus();
|
|
|
|
|
|
2014-10-13 15:38:36 +02:00
|
|
|
|
/******************************************************************************/
|
2014-07-13 02:32:44 +02:00
|
|
|
|
|
2014-09-28 20:58:26 +02:00
|
|
|
|
// https://www.youtube.com/watch?v=sociXdKnyr8
|
|
|
|
|
|
2014-07-13 02:32:44 +02:00
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
})();
|