mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-17 16:02:33 +01:00
826 lines
24 KiB
JavaScript
826 lines
24 KiB
JavaScript
/*******************************************************************************
|
||
|
||
µ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
|
||
*/
|
||
|
||
/* global self, vAPI, CSS */
|
||
|
||
/******************************************************************************/
|
||
/******************************************************************************/
|
||
|
||
/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */
|
||
;(function(root) {
|
||
|
||
'use strict';
|
||
|
||
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;
|
||
};
|
||
}
|
||
|
||
}(self));
|
||
|
||
/******************************************************************************/
|
||
/******************************************************************************/
|
||
|
||
(function() {
|
||
|
||
'use strict';
|
||
|
||
/******************************************************************************/
|
||
|
||
// don't run in frames
|
||
if ( window.top !== window ) {
|
||
return;
|
||
}
|
||
|
||
var pickerRoot = document.getElementById(vAPI.sessionId);
|
||
|
||
if ( pickerRoot ) {
|
||
return;
|
||
}
|
||
|
||
var localMessager = vAPI.messaging.channel('element-picker.js');
|
||
|
||
var svgOcean = null;
|
||
var svgIslands = null;
|
||
var dialog = null;
|
||
var taCandidate = null;
|
||
|
||
var netFilterCandidates = [];
|
||
var cosmeticFilterCandidates = [];
|
||
|
||
var targetElements = [];
|
||
|
||
/******************************************************************************/
|
||
|
||
// 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 = '';
|
||
}
|
||
|
||
/******************************************************************************/
|
||
|
||
var safeQuerySelectorAll = function(node, selector) {
|
||
if ( node !== null ) {
|
||
try {
|
||
return node.querySelectorAll(selector);
|
||
} catch (e) {
|
||
}
|
||
}
|
||
return [];
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var pausePicker = function() {
|
||
dialog.parentNode.classList.add('paused');
|
||
svgListening(false);
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var unpausePicker = function() {
|
||
dialog.parentNode.classList.remove('paused');
|
||
svgListening(true);
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var highlightElements = function(elems, force) {
|
||
// To make mouse move handler more efficient
|
||
if ( !force && elems.length === targetElements.length ) {
|
||
if ( elems.length === 0 || elems[0] === targetElements[0] ) {
|
||
return;
|
||
}
|
||
}
|
||
targetElements = elems;
|
||
|
||
var ow = pickerRoot.contentWindow.innerWidth;
|
||
var oh = pickerRoot.contentWindow.innerHeight;
|
||
var ocean = [
|
||
'M0 0',
|
||
'h', ow,
|
||
'v', oh,
|
||
'h-', ow,
|
||
'z'
|
||
];
|
||
var islands = [];
|
||
|
||
var elem, rect, poly;
|
||
for ( var i = 0; i < elems.length; i++ ) {
|
||
elem = elems[i];
|
||
if ( elem === pickerRoot ) {
|
||
continue;
|
||
}
|
||
if ( typeof elem.getBoundingClientRect !== 'function' ) {
|
||
continue;
|
||
}
|
||
rect = elem.getBoundingClientRect();
|
||
|
||
// 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 +
|
||
'h' + rect.width +
|
||
'v' + rect.height +
|
||
'h-' + rect.width +
|
||
'z';
|
||
ocean.push(poly);
|
||
islands.push(poly);
|
||
}
|
||
svgOcean.setAttribute('d', ocean.join(''));
|
||
svgIslands.setAttribute('d', islands.join('') || 'M0 0');
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
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.
|
||
|
||
var netFilterFromElement = function(elem, out) {
|
||
if ( elem === null ) {
|
||
return;
|
||
}
|
||
if ( elem.nodeType !== 1 ) {
|
||
return;
|
||
}
|
||
var tagName = elem.tagName.toLowerCase();
|
||
if ( netFilterSources.hasOwnProperty(tagName) === false ) {
|
||
return;
|
||
}
|
||
var src = elem[netFilterSources[tagName]];
|
||
if ( src.length === 0 ) {
|
||
return;
|
||
}
|
||
// Remove fragment
|
||
var pos = src.indexOf('#');
|
||
if ( pos !== -1 ) {
|
||
src = src.slice(0, pos);
|
||
}
|
||
// 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);
|
||
}
|
||
};
|
||
|
||
var netFilterSources = {
|
||
'embed': 'src',
|
||
'iframe': 'src',
|
||
'img': 'src',
|
||
'object': 'data'
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
// Extract the best possible cosmetic filter, i.e. as specific as possible.
|
||
|
||
var cosmeticFilterFromElement = function(elem, out) {
|
||
if ( elem === null ) {
|
||
return;
|
||
}
|
||
if ( elem.nodeType !== 1 ) {
|
||
return;
|
||
}
|
||
var tagName = elem.tagName.toLowerCase();
|
||
var prefix = '';
|
||
var suffix = [];
|
||
var v, i;
|
||
|
||
// 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+/);
|
||
i = v.length;
|
||
while ( i-- ) {
|
||
v[i] = CSS.escape(v[i]);
|
||
}
|
||
suffix.push('.', v.join('.'));
|
||
}
|
||
|
||
if ( suffix.length === 0 ) {
|
||
prefix = tagName;
|
||
}
|
||
|
||
// Attributes (depends on tag name)
|
||
var attributes = [], attr;
|
||
switch ( tagName ) {
|
||
case 'a':
|
||
v = elem.getAttribute('href');
|
||
if ( v ) {
|
||
v = v.replace(/\?.*$/, '');
|
||
if ( v.length ) {
|
||
attributes.push({ k: 'href', v: v });
|
||
}
|
||
}
|
||
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, '"]');
|
||
}
|
||
}
|
||
|
||
var selector = prefix + suffix.join('');
|
||
|
||
// https://github.com/gorhill/uBlock/issues/637
|
||
// 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.
|
||
var parentNode = elem.parentNode;
|
||
if ( safeQuerySelectorAll(parentNode, cssScope + selector).length > 1 ) {
|
||
i = 1;
|
||
while ( elem.previousSibling !== null ) {
|
||
elem = elem.previousSibling;
|
||
if ( typeof elem.tagName !== 'string' ) {
|
||
continue;
|
||
}
|
||
if ( elem.tagName.toLowerCase() !== tagName ) {
|
||
continue;
|
||
}
|
||
i++;
|
||
}
|
||
selector += ':nth-of-type(' + i + ')';
|
||
}
|
||
|
||
out.push('##' + selector);
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var filtersFromElement = function(elem) {
|
||
netFilterCandidates.length = 0;
|
||
cosmeticFilterCandidates.length = 0;
|
||
while ( elem && elem !== document.body ) {
|
||
netFilterFromElement(elem, netFilterCandidates);
|
||
cosmeticFilterFromElement(elem, cosmeticFilterCandidates);
|
||
elem = elem.parentNode;
|
||
}
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var elementsFromFilter = function(filter) {
|
||
var out = [];
|
||
|
||
filter = filter.trim();
|
||
if ( filter === '' ) {
|
||
return 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) {
|
||
}
|
||
return out;
|
||
}
|
||
|
||
// 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('iframe, img, object, embed');
|
||
var i = elems.length;
|
||
var elem, src;
|
||
while ( i-- ) {
|
||
elem = elems[i];
|
||
src = elem[netFilterSources[elem.tagName.toLowerCase()]];
|
||
if ( src && src.indexOf(filter) !== -1 ) {
|
||
out.push(elem);
|
||
}
|
||
}
|
||
return out;
|
||
};
|
||
|
||
// https://www.youtube.com/watch?v=YI2XuIOW3gM
|
||
|
||
/******************************************************************************/
|
||
|
||
var userFilterFromCandidate = function() {
|
||
var v = taCandidate.value;
|
||
|
||
var elems = elementsFromFilter(v);
|
||
if ( elems.length === 0 ) {
|
||
return false;
|
||
}
|
||
|
||
// Cosmetic filter?
|
||
if ( v.slice(0, 2) === '##' ) {
|
||
return window.location.hostname + v;
|
||
}
|
||
|
||
// If domain included in filter, no need for domain option
|
||
if ( v.slice(0, 2) === '||' ) {
|
||
return v;
|
||
}
|
||
// Assume net filter
|
||
return v + '$domain=' + window.location.hostname;
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var onCandidateChanged = function() {
|
||
var elems = elementsFromFilter(taCandidate.value);
|
||
dialog.querySelector('#create').disabled = elems.length === 0;
|
||
highlightElements(elems);
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var candidateFromFilterChoice = function(filterChoice) {
|
||
var slot = filterChoice.slot;
|
||
var filters = filterChoice.filters;
|
||
var filter = filters[slot];
|
||
|
||
if ( filter === undefined ) {
|
||
return '';
|
||
}
|
||
|
||
// For net filters there no such thing as a path
|
||
if ( filterChoice.type === 'net' || filterChoice.modifier ) {
|
||
return filter;
|
||
}
|
||
|
||
// Return path: the target element, then all siblings prepended
|
||
var selector = [];
|
||
for ( ; slot < filters.length; slot++ ) {
|
||
filter = filters[slot];
|
||
selector.unshift(filter.replace(/^##/, ''));
|
||
// Stop at any element with an id: these are unique in a web page
|
||
if ( filter.slice(0, 3) === '###' ) {
|
||
break;
|
||
}
|
||
}
|
||
return '##' + selector.join(' > ');
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
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;
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var onDialogClicked = function(ev) {
|
||
if ( ev.target === null ) {
|
||
/* do nothing */
|
||
}
|
||
|
||
else if ( ev.target.id === 'create' ) {
|
||
var filter = userFilterFromCandidate();
|
||
if ( filter ) {
|
||
localMessager.send({ what: 'createUserFilter', filters: filter });
|
||
removeElements(elementsFromFilter(taCandidate.value));
|
||
stopPicker();
|
||
}
|
||
}
|
||
|
||
else if ( ev.target.id === 'pick' ) {
|
||
unpausePicker();
|
||
}
|
||
|
||
else if ( ev.target.id === 'quit' ) {
|
||
stopPicker();
|
||
}
|
||
|
||
else if ( ev.target.parentNode.classList.contains('changeFilter') ) {
|
||
taCandidate.value = candidateFromFilterChoice(filterChoiceFromEvent(ev));
|
||
onCandidateChanged();
|
||
}
|
||
|
||
ev.stopPropagation();
|
||
ev.preventDefault();
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var removeAllChildren = function(parent) {
|
||
while ( parent.firstChild ) {
|
||
parent.removeChild(parent.firstChild);
|
||
}
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
// 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.
|
||
|
||
var showDialog = function(options) {
|
||
pausePicker();
|
||
|
||
options = options || {};
|
||
|
||
// Create lists of candidate filters
|
||
var populate = function(src, des) {
|
||
var root = dialog.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);
|
||
}
|
||
root.style.display = src.length !== 0 ? '' : 'none';
|
||
};
|
||
|
||
populate(netFilterCandidates, '#netFilters');
|
||
populate(cosmeticFilterCandidates, '#cosmeticFilters');
|
||
|
||
dialog.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none';
|
||
dialog.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;
|
||
}
|
||
|
||
taCandidate.value = '';
|
||
if ( filterChoice.type !== '' ) {
|
||
taCandidate.value = candidateFromFilterChoice(filterChoice);
|
||
onCandidateChanged();
|
||
}
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var elementFromPoint = function(x, y) {
|
||
pickerRoot.style.pointerEvents = 'none';
|
||
var elem = document.elementFromPoint(x, y);
|
||
if ( elem === document.body || elem === document.documentElement ) {
|
||
elem = null;
|
||
}
|
||
pickerRoot.style.pointerEvents = '';
|
||
return elem;
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var onSvgHovered = function(ev) {
|
||
var elem = elementFromPoint(ev.clientX, ev.clientY);
|
||
highlightElements(elem ? [elem] : []);
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var onSvgClicked = function(ev) {
|
||
var elem = elementFromPoint(ev.clientX, ev.clientY);
|
||
if ( elem === null ) {
|
||
return;
|
||
}
|
||
filtersFromElement(elem);
|
||
showDialog();
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var svgListening = function(on) {
|
||
var svg = dialog.ownerDocument.body.querySelector('svg');
|
||
var action = (on ? 'add' : 'remove') + 'EventListener';
|
||
svg[action]('mousemove', onSvgHovered);
|
||
svg[action]('click', onSvgClicked);
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var onKeyPressed = function(ev) {
|
||
if ( ev.which === 27 ) {
|
||
ev.stopPropagation();
|
||
ev.preventDefault();
|
||
stopPicker();
|
||
}
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
// https://github.com/gorhill/uBlock/issues/190
|
||
// May need to dynamically adjust the height of the overlay + new position
|
||
// of highlighted elements.
|
||
|
||
var onScrolled = function() {
|
||
highlightElements(targetElements, true);
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
// Let's have the element picker code flushed from memory when no longer
|
||
// in use: to ensure this, release all local references.
|
||
|
||
var stopPicker = function() {
|
||
targetElements = [];
|
||
|
||
if ( pickerRoot === null ) {
|
||
return;
|
||
}
|
||
|
||
window.removeEventListener('scroll', onScrolled, true);
|
||
pickerRoot.contentWindow.removeEventListener('keydown', onKeyPressed, true);
|
||
taCandidate.removeEventListener('input', onCandidateChanged);
|
||
dialog.removeEventListener('click', onDialogClicked);
|
||
svgListening(false);
|
||
pickerRoot.parentNode.removeChild(pickerRoot);
|
||
pickerRoot.onload = null;
|
||
pickerRoot =
|
||
dialog =
|
||
svgOcean = svgIslands =
|
||
taCandidate = null;
|
||
localMessager.close();
|
||
|
||
window.focus();
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
var startPicker = function(details) {
|
||
pickerRoot.onload = stopPicker;
|
||
|
||
var frameDoc = pickerRoot.contentDocument;
|
||
var parsedDom = (new DOMParser()).parseFromString(
|
||
details.frameContent,
|
||
'text/html'
|
||
);
|
||
|
||
frameDoc.replaceChild(
|
||
frameDoc.adoptNode(parsedDom.documentElement),
|
||
frameDoc.documentElement
|
||
);
|
||
|
||
frameDoc.body.setAttribute('lang', navigator.language);
|
||
|
||
dialog = frameDoc.body.querySelector('aside');
|
||
dialog.addEventListener('click', onDialogClicked);
|
||
|
||
taCandidate = dialog.querySelector('textarea');
|
||
taCandidate.addEventListener('input', onCandidateChanged);
|
||
|
||
var svgRoot = frameDoc.body.querySelector('svg');
|
||
svgOcean = svgRoot.firstChild;
|
||
svgIslands = svgRoot.lastChild;
|
||
svgListening(true);
|
||
|
||
window.addEventListener('scroll', onScrolled, true);
|
||
pickerRoot.contentWindow.addEventListener('keydown', onKeyPressed, true);
|
||
pickerRoot.contentWindow.focus();
|
||
|
||
// Auto-select a specific target, if any, and if possible
|
||
|
||
highlightElements([], true);
|
||
|
||
var elem;
|
||
|
||
// Try using mouse position
|
||
if ( details.clientX !== -1 ) {
|
||
elem = elementFromPoint(details.clientX, details.clientY);
|
||
if ( elem !== null ) {
|
||
filtersFromElement(elem);
|
||
showDialog();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 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',
|
||
'embed': '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;
|
||
}
|
||
};
|
||
|
||
/******************************************************************************/
|
||
|
||
pickerRoot = document.createElement('iframe');
|
||
pickerRoot.id = vAPI.sessionId;
|
||
pickerRoot.style.cssText = [
|
||
'display: block',
|
||
'visibility: visible',
|
||
'opacity: 1',
|
||
'position: fixed',
|
||
'top: 0',
|
||
'left: 0',
|
||
'width: 100%',
|
||
'height: 100%',
|
||
'background: transparent',
|
||
'margin: 0',
|
||
'padding: 0',
|
||
'border: 0',
|
||
'border-radius: 0',
|
||
'box-shadow: none',
|
||
'outline: 0',
|
||
'z-index: 2147483647',
|
||
''
|
||
].join('!important; ');
|
||
|
||
pickerRoot.onload = function() {
|
||
localMessager.send({ what: 'elementPickerArguments' }, startPicker);
|
||
};
|
||
|
||
document.documentElement.appendChild(pickerRoot);
|
||
|
||
/******************************************************************************/
|
||
|
||
// https://www.youtube.com/watch?v=sociXdKnyr8
|
||
|
||
/******************************************************************************/
|
||
|
||
})();
|