mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-22 18:32:45 +01:00
fix #2984
This commit is contained in:
parent
7f2ee32f25
commit
6112a68faf
@ -38,7 +38,7 @@
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["http://*/*", "https://*/*"],
|
||||
"js": ["js/vapi-client.js", "js/contentscript.js"],
|
||||
"js": ["js/vapi-client.js", "js/vapi-usercss.js", "js/contentscript.js"],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true
|
||||
},
|
||||
|
@ -853,22 +853,17 @@ vAPI.messaging.onPortMessage = (function() {
|
||||
if ( supportsUserStylesheets ) {
|
||||
details.cssOrigin = 'user';
|
||||
}
|
||||
var fn;
|
||||
if ( msg.add ) {
|
||||
details.runAt = 'document_start';
|
||||
fn = chrome.tabs.insertCSS;
|
||||
} else {
|
||||
fn = chrome.tabs.removeCSS;
|
||||
}
|
||||
var css = msg.css;
|
||||
if ( typeof css === 'string' ) {
|
||||
details.code = css;
|
||||
fn(tabId, details);
|
||||
return;
|
||||
var cssText;
|
||||
for ( cssText of msg.add ) {
|
||||
details.code = cssText;
|
||||
chrome.tabs.insertCSS(tabId, details);
|
||||
}
|
||||
for ( var i = 0, n = css.length; i < n; i++ ) {
|
||||
details.code = css[i];
|
||||
fn(tabId, details);
|
||||
for ( cssText of msg.remove ) {
|
||||
details.code = cssText;
|
||||
chrome.tabs.removeCSS(tabId, details);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -171,14 +171,15 @@ vAPI.messaging = {
|
||||
|
||||
portPoller: function() {
|
||||
this.portTimer = null;
|
||||
if ( this.port !== null ) {
|
||||
if ( this.channelCount !== 0 || this.pendingCount !== 0 ) {
|
||||
this.portTimer = vAPI.setTimeout(this.portPollerCallback, this.portTimerDelay);
|
||||
this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.port !== null &&
|
||||
this.channelCount === 0 &&
|
||||
this.pendingCount === 0
|
||||
) {
|
||||
return this.destroyPort();
|
||||
}
|
||||
this.destroyPort();
|
||||
this.portTimer = vAPI.setTimeout(this.portPollerCallback, this.portTimerDelay);
|
||||
this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000);
|
||||
},
|
||||
portPollerCallback: null,
|
||||
|
||||
@ -324,15 +325,12 @@ vAPI.messaging = {
|
||||
|
||||
sendToChannelListeners: function(channelName, msg) {
|
||||
var listeners = this.channels[channelName];
|
||||
if ( listeners === undefined ) {
|
||||
return;
|
||||
}
|
||||
if ( listeners === undefined ) { return; }
|
||||
listeners = listeners.slice(0);
|
||||
var response;
|
||||
for ( var i = 0, n = listeners.length; i < n; i++ ) {
|
||||
response = listeners[i](msg);
|
||||
if ( response !== undefined ) {
|
||||
break;
|
||||
}
|
||||
for ( var listener of listeners ) {
|
||||
response = listener(msg);
|
||||
if ( response !== undefined ) { break; }
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
506
platform/chromium/vapi-usercss.js
Normal file
506
platform/chromium/vapi-usercss.js
Normal file
@ -0,0 +1,506 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2017 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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// For content pages
|
||||
|
||||
// Abort execution if our global vAPI object does not exist.
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/456
|
||||
// https://github.com/gorhill/uBlock/issues/2029
|
||||
|
||||
if ( typeof vAPI === 'object' ) { // >>>>>>>> start of HUGE-IF-BLOCK
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.DOMFilterer = function() {
|
||||
this.commitTimer = new vAPI.SafeAnimationFrame(this.commitNow.bind(this));
|
||||
this.domIsReady = document.readyState !== 'loading';
|
||||
this.listeners = [];
|
||||
this.hideNodeId = vAPI.randomToken();
|
||||
this.hideNodeStylesheet = false;
|
||||
this.excludedNodeSet = new WeakSet();
|
||||
this.addedNodes = new Set();
|
||||
this.removedNodes = false;
|
||||
|
||||
this.specificSimpleHide = new Set();
|
||||
this.specificSimpleHideAggregated = undefined;
|
||||
this.addedSpecificSimpleHide = [];
|
||||
this.specificComplexHide = new Set();
|
||||
this.specificComplexHideAggregated = undefined;
|
||||
this.addedSpecificComplexHide = [];
|
||||
|
||||
this.genericSimpleHide = new Set();
|
||||
this.genericComplexHide = new Set();
|
||||
|
||||
this.userStylesheet = {
|
||||
style: null,
|
||||
css: new Map(),
|
||||
disabled: false,
|
||||
add: function(cssText) {
|
||||
if ( cssText === '' || this.css.has(cssText) ) { return; }
|
||||
if ( this.style === null ) {
|
||||
this.style = document.createElement('style');
|
||||
this.style.disabled = this.disabled;
|
||||
var parent = document.head || document.documentElement;
|
||||
if ( parent !== null ) {
|
||||
parent.appendChild(this.style);
|
||||
}
|
||||
}
|
||||
var sheet = this.style.sheet,
|
||||
i = sheet.cssRules.length;
|
||||
if ( !sheet ) { return; }
|
||||
sheet.insertRule(cssText, i);
|
||||
this.css.set(cssText, sheet.cssRules[i]);
|
||||
},
|
||||
remove: function(cssText) {
|
||||
if ( cssText === '' ) { return; }
|
||||
var cssRule = this.css.get(cssText);
|
||||
if ( cssRule === undefined ) { return; }
|
||||
this.css.delete(cssText);
|
||||
if ( this.style === null ) { return; }
|
||||
var rules = this.style.sheet.cssRules,
|
||||
i = rules.length;
|
||||
while ( i-- ) {
|
||||
if ( rules[i] !== cssRule ) { continue; }
|
||||
this.style.sheet.deleteRule(i);
|
||||
break;
|
||||
}
|
||||
if ( rules.length === 0 ) {
|
||||
var parent = this.style.parentNode;
|
||||
if ( parent !== null ) {
|
||||
parent.removeChild(this.style);
|
||||
}
|
||||
this.style = null;
|
||||
}
|
||||
},
|
||||
toggle: function(state) {
|
||||
if ( state === undefined ) { state = this.disabled; }
|
||||
if ( state !== this.disabled ) { return; }
|
||||
this.disabled = !state;
|
||||
if ( this.style !== null ) {
|
||||
this.style.disabled = this.disabled;
|
||||
}
|
||||
},
|
||||
getAllSelectors: function() {
|
||||
var out = [];
|
||||
var rules = this.style &&
|
||||
this.style.sheet &&
|
||||
this.style.sheet.cssRules;
|
||||
if ( rules instanceof Object === false ) { return out; }
|
||||
var i = rules.length;
|
||||
while ( i-- ) {
|
||||
out.push(rules.item(i).selectorText);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
this.hideNodeExpando = undefined;
|
||||
this.hideNodeBatchProcessTimer = undefined;
|
||||
this.hiddenNodeObserver = undefined;
|
||||
this.hiddenNodesetToProcess = new Set();
|
||||
this.hiddenNodeset = new WeakSet();
|
||||
|
||||
if ( vAPI.domWatcher instanceof Object ) {
|
||||
vAPI.domWatcher.addListener(this);
|
||||
}
|
||||
};
|
||||
|
||||
vAPI.DOMFilterer.prototype = {
|
||||
reHideStyle: /^display: none !important;$/,
|
||||
|
||||
// https://www.w3.org/community/webed/wiki/CSS/Selectors#Combinators
|
||||
reCSSCombinators: /[ >+~]/,
|
||||
|
||||
commitNow: function() {
|
||||
this.commitTimer.clear();
|
||||
|
||||
if ( this.domIsReady !== true || this.userStylesheet.disabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var nodes, node;
|
||||
|
||||
// Filterset changed.
|
||||
|
||||
if ( this.addedSpecificSimpleHide.length !== 0 ) {
|
||||
console.time('specific simple filterset changed');
|
||||
console.log('added %d specific simple selectors', this.addedSpecificSimpleHide.length);
|
||||
nodes = document.querySelectorAll(this.addedSpecificSimpleHide.join(','));
|
||||
for ( node of nodes ) {
|
||||
this.hideNode(node);
|
||||
}
|
||||
this.addedSpecificSimpleHide = [];
|
||||
this.specificSimpleHideAggregated = undefined;
|
||||
console.timeEnd('specific simple filterset changed');
|
||||
}
|
||||
|
||||
if ( this.addedSpecificComplexHide.length !== 0 ) {
|
||||
console.time('specific complex filterset changed');
|
||||
console.log('added %d specific complex selectors', this.addedSpecificComplexHide.length);
|
||||
nodes = document.querySelectorAll(this.addedSpecificComplexHide.join(','));
|
||||
for ( node of nodes ) {
|
||||
this.hideNode(node);
|
||||
}
|
||||
this.addedSpecificComplexHide = [];
|
||||
this.specificComplexHideAggregated = undefined;
|
||||
console.timeEnd('specific complex filterset changed');
|
||||
}
|
||||
|
||||
// DOM layout changed.
|
||||
|
||||
var domNodesAdded = this.addedNodes.size !== 0,
|
||||
domLayoutChanged = domNodesAdded || this.removedNodes;
|
||||
|
||||
if ( domNodesAdded === false || domLayoutChanged === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('%d nodes added', this.addedNodes.size);
|
||||
|
||||
if ( this.specificSimpleHide.size !== 0 && domNodesAdded ) {
|
||||
console.time('dom layout changed/specific simple selectors');
|
||||
if ( this.specificSimpleHideAggregated === undefined ) {
|
||||
this.specificSimpleHideAggregated =
|
||||
Array.from(this.specificSimpleHide).join(',\n');
|
||||
}
|
||||
for ( node of this.addedNodes ) {
|
||||
if ( node[vAPI.matchesProp](this.specificSimpleHideAggregated) ) {
|
||||
this.hideNode(node);
|
||||
}
|
||||
nodes = node.querySelectorAll(this.specificSimpleHideAggregated);
|
||||
for ( node of nodes ) {
|
||||
this.hideNode(node);
|
||||
}
|
||||
}
|
||||
console.timeEnd('dom layout changed/specific simple selectors');
|
||||
}
|
||||
|
||||
if ( this.specificComplexHide.size !== 0 && domLayoutChanged ) {
|
||||
console.time('dom layout changed/specific complex selectors');
|
||||
if ( this.specificComplexHideAggregated === undefined ) {
|
||||
this.specificComplexHideAggregated =
|
||||
Array.from(this.specificComplexHide).join(',\n');
|
||||
}
|
||||
nodes = document.querySelectorAll(this.specificComplexHideAggregated);
|
||||
for ( node of nodes ) {
|
||||
this.hideNode(node);
|
||||
}
|
||||
console.timeEnd('dom layout changed/specific complex selectors');
|
||||
}
|
||||
|
||||
this.addedNodes.clear();
|
||||
this.removedNodes = false;
|
||||
},
|
||||
|
||||
commit: function(now) {
|
||||
if ( now ) {
|
||||
this.commitTimer.clear();
|
||||
this.commitNow();
|
||||
} else {
|
||||
this.commitTimer.start();
|
||||
}
|
||||
},
|
||||
|
||||
addCSSRule: function(selectors, declarations, details) {
|
||||
if ( selectors === undefined ) { return; }
|
||||
|
||||
if ( details === undefined ) { details = {}; }
|
||||
|
||||
var isGeneric= details.lazy === true,
|
||||
isSimple = details.type === 'simple',
|
||||
isComplex = details.type === 'complex',
|
||||
selector;
|
||||
|
||||
var selectorsStr = Array.isArray(selectors) ?
|
||||
selectors.join(',\n') :
|
||||
selectors;
|
||||
if ( selectorsStr.length === 0 ) { return; }
|
||||
|
||||
this.userStylesheet.add(
|
||||
selectorsStr +
|
||||
'\n{ ' + declarations + ' }'
|
||||
);
|
||||
this.commit();
|
||||
|
||||
this.triggerListeners('declarative', selectorsStr);
|
||||
|
||||
if ( this.reHideStyle.test(declarations) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isGeneric ) {
|
||||
if ( isSimple ) {
|
||||
this.genericSimpleHide.add(selectorsStr);
|
||||
return;
|
||||
}
|
||||
if ( isComplex ) {
|
||||
this.genericComplexHide.add(selectorsStr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var selectorsArr = Array.isArray(selectors) ?
|
||||
selectors :
|
||||
selectors.split(',\n');
|
||||
|
||||
if ( isGeneric ) {
|
||||
for ( selector of selectorsArr ) {
|
||||
if ( this.reCSSCombinators.test(selector) ) {
|
||||
this.genericComplexHide.add(selector);
|
||||
} else {
|
||||
this.genericSimpleHide.add(selector);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Specific cosmetic filters.
|
||||
for ( selector of selectorsArr ) {
|
||||
if (
|
||||
isComplex ||
|
||||
isSimple === false && this.reCSSCombinators.test(selector)
|
||||
) {
|
||||
if ( this.specificComplexHide.has(selector) === false ) {
|
||||
this.specificComplexHide.add(selector);
|
||||
this.addedSpecificComplexHide.push(selector);
|
||||
}
|
||||
} else if ( this.specificSimpleHide.has(selector) === false ) {
|
||||
this.specificSimpleHide.add(selector);
|
||||
this.addedSpecificSimpleHide.push(selector);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
removeCSSRule: function(selectors, declarations) {
|
||||
var selectorsStr = Array.isArray(selectors)
|
||||
? selectors.join(',\n')
|
||||
: selectors;
|
||||
if ( selectorsStr.length === 0 ) { return; }
|
||||
this.userStylesheet.remove(
|
||||
selectorsStr +
|
||||
'\n{ ' + declarations + ' }'
|
||||
);
|
||||
if ( this.reHideStyle.test(declarations) === false ) { return; }
|
||||
var selectorsArr = Array.isArray(selectors) ?
|
||||
selectors :
|
||||
selectors.split(',\n');
|
||||
for ( var selector of selectorsArr ) {
|
||||
if ( this.reCSSCombinators.test(selector) ) {
|
||||
this.specificComplexHide.remove(selector);
|
||||
this.genericComplexHide.remove(selector);
|
||||
} else {
|
||||
this.specificSimpleHide.remove(selector);
|
||||
this.genericSimpleHide.remove(selector);
|
||||
}
|
||||
}
|
||||
this.commit();
|
||||
},
|
||||
|
||||
onDOMCreated: function() {
|
||||
this.domIsReady = true;
|
||||
this.addedNodes.clear();
|
||||
this.removedNodes = false;
|
||||
this.commit();
|
||||
},
|
||||
|
||||
onDOMChanged: function(addedNodes, removedNodes) {
|
||||
for ( var node of addedNodes ) {
|
||||
this.addedNodes.add(node);
|
||||
}
|
||||
this.removedNodes = this.removedNodes || removedNodes;
|
||||
this.commit();
|
||||
},
|
||||
|
||||
addListener: function(listener) {
|
||||
if ( this.listeners.indexOf(listener) !== -1 ) { return; }
|
||||
this.listeners.push(listener);
|
||||
},
|
||||
|
||||
removeListener: function(listener) {
|
||||
var pos = this.listeners.indexOf(listener);
|
||||
if ( pos === -1 ) { return; }
|
||||
this.listeners.splice(pos, 1);
|
||||
},
|
||||
|
||||
triggerListeners: function(type, selectors) {
|
||||
var i = this.listeners.length;
|
||||
while ( i-- ) {
|
||||
this.listeners[i].onFiltersetChanged(type, selectors);
|
||||
}
|
||||
},
|
||||
|
||||
// https://jsperf.com/clientheight-and-clientwidth-vs-getcomputedstyle
|
||||
// Avoid getComputedStyle(), detecting whether a node is visible can be
|
||||
// achieved with clientWidth/clientHeight.
|
||||
// https://gist.github.com/paulirish/5d52fb081b3570c81e3a
|
||||
// Do not interleave read-from/write-to the DOM. Write-to DOM
|
||||
// operations would cause the first read-from to be expensive, and
|
||||
// interleaving means that potentially all single read-from operation
|
||||
// would be expensive rather than just the 1st one.
|
||||
// Benchmarking toggling off/on cosmetic filtering confirms quite an
|
||||
// improvement when:
|
||||
// - batching as much as possible handling of all nodes;
|
||||
// - avoiding to interleave read-from/write-to operations.
|
||||
// However, toggling off/on cosmetic filtering repeatedly is not
|
||||
// a real use case, but this shows this will help performance
|
||||
// on sites which try to use inline styles to bypass blockers.
|
||||
hideNodeBatchProcess: function() {
|
||||
this.hideNodeBatchProcessTimer.clear();
|
||||
var expando = this.hideNodeExpando;
|
||||
for ( var node of this.hiddenNodesetToProcess ) {
|
||||
if (
|
||||
this.hiddenNodeset.has(node) === false ||
|
||||
node[expando] === undefined ||
|
||||
node.clientHeight === 0 || node.clientWidth === 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
var attr = node.getAttribute('style');
|
||||
if ( attr === null ) {
|
||||
attr = '';
|
||||
} else if (
|
||||
attr.length !== 0 &&
|
||||
attr.charCodeAt(attr.length - 1) !== 0x3B /* ';' */
|
||||
) {
|
||||
attr += '; ';
|
||||
}
|
||||
node.setAttribute('style', attr + 'display: none !important;');
|
||||
}
|
||||
this.hiddenNodesetToProcess.clear();
|
||||
},
|
||||
|
||||
hideNodeObserverHandler: function(mutations) {
|
||||
if ( this.userStylesheet.disabled ) { return; }
|
||||
var i = mutations.length,
|
||||
stagedNodes = this.hiddenNodesetToProcess;
|
||||
while ( i-- ) {
|
||||
stagedNodes.add(mutations[i].target);
|
||||
}
|
||||
this.hideNodeBatchProcessTimer.start();
|
||||
},
|
||||
|
||||
hiddenNodeObserverOptions: {
|
||||
attributes: true,
|
||||
attributeFilter: [ 'style' ]
|
||||
},
|
||||
|
||||
hideNodeInit: function() {
|
||||
this.hideNodeExpando = vAPI.randomToken();
|
||||
this.hideNodeBatchProcessTimer =
|
||||
new vAPI.SafeAnimationFrame(this.hideNodeBatchProcess.bind(this));
|
||||
this.hiddenNodeObserver =
|
||||
new MutationObserver(this.hideNodeObserverHandler.bind(this));
|
||||
if ( this.hideNodeStylesheet === false ) {
|
||||
this.hideNodeStylesheet = true;
|
||||
this.userStylesheet.add(
|
||||
'[' + this.hideNodeId + ']\n{ display: none !important; }'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
excludeNode: function(node) {
|
||||
this.excludedNodeSet.add(node);
|
||||
this.unhideNode(node);
|
||||
},
|
||||
|
||||
hideNode: function(node) {
|
||||
if ( this.excludedNodeSet.has(node) ) { return; }
|
||||
if ( this.hiddenNodeset.has(node) ) { return; }
|
||||
this.hiddenNodeset.add(node);
|
||||
if ( this.hideNodeExpando === undefined ) { this.hideNodeInit(); }
|
||||
node.setAttribute(this.hideNodeId, '');
|
||||
if ( node[this.hideNodeExpando] === undefined ) {
|
||||
node[this.hideNodeExpando] =
|
||||
node.hasAttribute('style') &&
|
||||
(node.getAttribute('style') || '');
|
||||
}
|
||||
this.hiddenNodesetToProcess.add(node);
|
||||
this.hideNodeBatchProcessTimer.start();
|
||||
this.hiddenNodeObserver.observe(node, this.hiddenNodeObserverOptions);
|
||||
},
|
||||
|
||||
unhideNode: function(node) {
|
||||
if ( this.hiddenNodeset.has(node) === false ) { return; }
|
||||
node.removeAttribute(this.hideNodeId);
|
||||
this.hiddenNodesetToProcess.delete(node);
|
||||
if ( this.hideNodeExpando === undefined ) { return; }
|
||||
var attr = node[this.hideNodeExpando];
|
||||
if ( attr === false ) {
|
||||
node.removeAttribute('style');
|
||||
} else if ( typeof attr === 'string' ) {
|
||||
node.setAttribute('style', attr);
|
||||
}
|
||||
node[this.hideNodeExpando] = undefined;
|
||||
this.hiddenNodeset.delete(node);
|
||||
},
|
||||
|
||||
showNode: function(node) {
|
||||
var attr = node[this.hideNodeExpando];
|
||||
if ( attr === false ) {
|
||||
node.removeAttribute('style');
|
||||
} else if ( typeof attr === 'string' ) {
|
||||
node.setAttribute('style', attr);
|
||||
}
|
||||
},
|
||||
|
||||
unshowNode: function(node) {
|
||||
this.hiddenNodesetToProcess.add(node);
|
||||
},
|
||||
|
||||
toggle: function(state) {
|
||||
this.userStylesheet.toggle(state);
|
||||
var disabled = this.userStylesheet.disabled,
|
||||
nodes = document.querySelectorAll('[' + this.hideNodeId + ']');
|
||||
for ( var node of nodes ) {
|
||||
if ( disabled ) {
|
||||
this.showNode(node);
|
||||
} else {
|
||||
this.unshowNode(node);
|
||||
}
|
||||
}
|
||||
if ( disabled === false && this.hideNodeExpando !== undefined ) {
|
||||
this.hideNodeBatchProcessTimer.start();
|
||||
}
|
||||
},
|
||||
|
||||
getFilteredElementCount: function() {
|
||||
return document.querySelectorAll(
|
||||
this.userStylesheet.getAllSelectors().join(',\n')
|
||||
).length;
|
||||
},
|
||||
|
||||
getAllDeclarativeSelectors: function() {
|
||||
return [].concat(
|
||||
Array.from(this.specificSimpleHide),
|
||||
Array.from(this.specificComplexHide),
|
||||
Array.from(this.genericSimpleHide),
|
||||
Array.from(this.genericComplexHide)
|
||||
).join(',\n');
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
} // <<<<<<<< end of HUGE-IF-BLOCK
|
@ -566,6 +566,7 @@ var contentObserver = {
|
||||
let sandbox = this.initContentScripts(win, true);
|
||||
try {
|
||||
lss(this.contentBaseURI + 'vapi-client.js', sandbox);
|
||||
lss(this.contentBaseURI + 'vapi-usercss.js', sandbox);
|
||||
lss(this.contentBaseURI + 'contentscript.js', sandbox);
|
||||
} catch (ex) {
|
||||
//console.exception(ex.msg, ex.stack);
|
||||
|
@ -119,6 +119,36 @@ vAPI.shutdown = {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var insertUserCSS = self.injectCSS || function(){},
|
||||
removeUserCSS = self.removeCSS || function(){};
|
||||
|
||||
var processUserCSS = function(details, callback) {
|
||||
var cssText;
|
||||
var aa = details.add;
|
||||
if ( Array.isArray(aa) ) {
|
||||
for ( cssText of aa ) {
|
||||
insertUserCSS(
|
||||
'data:text/css;charset=utf-8,' +
|
||||
encodeURIComponent(cssText)
|
||||
);
|
||||
}
|
||||
}
|
||||
aa = details.remove;
|
||||
if ( Array.isArray(aa) ) {
|
||||
for ( cssText of aa ) {
|
||||
removeUserCSS(
|
||||
'data:text/css;charset=utf-8,' +
|
||||
encodeURIComponent(cssText)
|
||||
);
|
||||
}
|
||||
}
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.messaging = {
|
||||
channels: Object.create(null),
|
||||
channelCount: 0,
|
||||
@ -276,6 +306,10 @@ vAPI.messaging = {
|
||||
},
|
||||
|
||||
send: function(channelName, message, callback) {
|
||||
// User stylesheets are handled content-side on legacy Firefox.
|
||||
if ( channelName === 'vapi-background' && message.what === 'userCSS' ) {
|
||||
return processUserCSS(message, callback);
|
||||
}
|
||||
this.sendTo(channelName, message, undefined, undefined, callback);
|
||||
},
|
||||
|
||||
@ -358,15 +392,12 @@ vAPI.messaging = {
|
||||
|
||||
sendToChannelListeners: function(channelName, msg) {
|
||||
var listeners = this.channels[channelName];
|
||||
if ( listeners === undefined ) {
|
||||
return;
|
||||
}
|
||||
if ( listeners === undefined ) { return; }
|
||||
listeners = listeners.slice(0);
|
||||
var response;
|
||||
for ( var i = 0, n = listeners.length; i < n; i++ ) {
|
||||
response = listeners[i](msg);
|
||||
if ( response !== undefined ) {
|
||||
break;
|
||||
}
|
||||
for ( var listener of listeners ) {
|
||||
response = listener(msg);
|
||||
if ( response !== undefined ) { break; }
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@ -378,46 +409,6 @@ vAPI.messaging.start();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if ( self.injectCSS ) {
|
||||
vAPI.userCSS = {
|
||||
_userCSS: '',
|
||||
_sheetURI: '',
|
||||
_load: function() {
|
||||
if ( this._userCSS === '' || this._sheetURI !== '' ) { return; }
|
||||
this._sheetURI = 'data:text/css;charset=utf-8,' + encodeURIComponent(this._userCSS);
|
||||
self.injectCSS(this._sheetURI);
|
||||
},
|
||||
_unload: function() {
|
||||
if ( this._sheetURI === '' ) { return; }
|
||||
self.removeCSS(this._sheetURI);
|
||||
this._sheetURI = '';
|
||||
},
|
||||
add: function(cssText) {
|
||||
if ( cssText === '' ) { return; }
|
||||
if ( this._userCSS !== '' ) { this._userCSS += '\n'; }
|
||||
this._userCSS += cssText;
|
||||
this._unload();
|
||||
this._load();
|
||||
},
|
||||
remove: function(cssText) {
|
||||
if ( cssText === '' || this._userCSS === '' ) { return; }
|
||||
this._userCSS = this._userCSS.replace(cssText, '').trim();
|
||||
this._unload();
|
||||
this._load();
|
||||
},
|
||||
toggle: function(state) {
|
||||
if ( this._userCSS === '' ) { return; }
|
||||
if ( state === undefined ) {
|
||||
state = this._sheetURI === '';
|
||||
}
|
||||
return state ? this._load() : this._unload();
|
||||
}
|
||||
};
|
||||
vAPI.hideNode = vAPI.unhideNode = function(){};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=444165
|
||||
// https://github.com/gorhill/uBlock/issues/2256
|
||||
// Not the prettiest solution, but that's the safest/simplest I can think
|
||||
|
@ -23,41 +23,222 @@
|
||||
|
||||
// For content pages
|
||||
|
||||
if ( typeof vAPI === 'object' ) { // >>>>>>>> start of HUGE-IF-BLOCK
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
(function() {
|
||||
if ( typeof vAPI !== 'object' ) { return; }
|
||||
vAPI.DOMFilterer = function() {
|
||||
this.commitTimer = new vAPI.SafeAnimationFrame(this.commitNow.bind(this));
|
||||
this.domIsReady = document.readyState !== 'loading';
|
||||
this.listeners = [];
|
||||
this.hideNodeId = vAPI.randomToken();
|
||||
this.hideNodeStylesheet = false;
|
||||
this.excludedNodeSet = new WeakSet();
|
||||
this.addedCSSRules = [];
|
||||
this.removedCSSRules = [];
|
||||
this.internalRules = new Set();
|
||||
|
||||
vAPI.userCSS = {
|
||||
_userCSS: new Set(),
|
||||
_disabled: false,
|
||||
_send: function(add, css) {
|
||||
vAPI.messaging.send('vapi-background', {
|
||||
what: 'userCSS',
|
||||
add: add,
|
||||
css: css
|
||||
});
|
||||
this.userStylesheets = {
|
||||
current: new Set(),
|
||||
added: new Set(),
|
||||
removed: new Set(),
|
||||
disabled: false,
|
||||
apply: function() {
|
||||
for ( let cssText of this.added ) {
|
||||
if ( this.current.has(cssText) || this.removed.has(cssText) ) {
|
||||
this.added.delete(cssText);
|
||||
} else {
|
||||
this.current.add(cssText);
|
||||
}
|
||||
}
|
||||
for ( let cssText of this.removed ) {
|
||||
if ( this.current.has(cssText) === false ) {
|
||||
this.removed.delete(cssText);
|
||||
} else {
|
||||
this.current.delete(cssText);
|
||||
}
|
||||
}
|
||||
if ( this.added.size === 0 && this.removed.size === 0 ) { return; }
|
||||
if ( this.disabled === false ) {
|
||||
vAPI.messaging.send('vapi-background', {
|
||||
what: 'userCSS',
|
||||
add: Array.from(this.added),
|
||||
remove: Array.from(this.removed)
|
||||
});
|
||||
}
|
||||
this.added.clear();
|
||||
this.removed.clear();
|
||||
},
|
||||
add: function(cssText) {
|
||||
if ( cssText === '' || this._userCSS.has(cssText) ) { return; }
|
||||
this._userCSS.add(cssText);
|
||||
if ( this._disabled ) { return; }
|
||||
this._send(true, cssText);
|
||||
if ( cssText === '' ) { return; }
|
||||
this.added.add(cssText);
|
||||
},
|
||||
remove: function(cssText) {
|
||||
if ( cssText === '' ) { return; }
|
||||
if ( this._userCSS.delete(cssText) && !this._disabled ) {
|
||||
this._send(true, cssText);
|
||||
this._send(false, cssText);
|
||||
}
|
||||
this.removed.add(cssText);
|
||||
},
|
||||
toggle: function(state) {
|
||||
if ( state === undefined ) { state = this._disabled; }
|
||||
if ( state !== this._disabled ) { return; }
|
||||
this._disabled = !state;
|
||||
if ( this._userCSS.size === 0 ) { return; }
|
||||
this._send(state, Array.from(this._userCSS));
|
||||
if ( state === undefined ) { state = this.disabled; }
|
||||
if ( state !== this.disabled ) { return; }
|
||||
this.disabled = !state;
|
||||
if ( this.current.size === 0 ) { return; }
|
||||
var all = Array.from(this.current);
|
||||
var toAdd = [], toRemove = [];
|
||||
if ( this.disabled ) {
|
||||
toRemove = all;
|
||||
} else {
|
||||
toAdd = all;
|
||||
}
|
||||
vAPI.messaging.send('vapi-background', {
|
||||
what: 'userCSS',
|
||||
add: toAdd,
|
||||
remove: toRemove
|
||||
});
|
||||
}
|
||||
};
|
||||
vAPI.hideNode = vAPI.unhideNode = function(){};
|
||||
})();
|
||||
|
||||
if ( this.domIsReady !== true ) {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
this.domIsReady = true;
|
||||
this.commit();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
vAPI.DOMFilterer.prototype = {
|
||||
reOnlySelectors: /\n\{[^\n]+/g,
|
||||
commitNow: function() {
|
||||
this.commitTimer.clear();
|
||||
var i, entry, ruleText;
|
||||
i = this.addedCSSRules.length;
|
||||
while ( i-- ) {
|
||||
entry = this.addedCSSRules[i];
|
||||
if ( entry.lazy !== true || this.domIsReady ) {
|
||||
ruleText = entry.selectors + '\n{ ' + entry.declarations + ' }';
|
||||
this.userStylesheets.add(ruleText);
|
||||
this.addedCSSRules.splice(i, 1);
|
||||
if ( entry.internal ) {
|
||||
this.internalRules.add(ruleText);
|
||||
}
|
||||
}
|
||||
}
|
||||
i = this.removedCSSRules.length;
|
||||
while ( i-- ) {
|
||||
entry = this.removedCSSRules[i];
|
||||
ruleText = entry.selectors + '\n{ ' + entry.declarations + ' }';
|
||||
this.userStylesheets.remove(ruleText);
|
||||
this.internalRules.delete(ruleText);
|
||||
}
|
||||
this.removedCSSRules = [];
|
||||
this.userStylesheets.apply();
|
||||
},
|
||||
|
||||
commit: function(commitNow) {
|
||||
if ( commitNow ) {
|
||||
this.commitTimer.clear();
|
||||
this.commitNow();
|
||||
} else {
|
||||
this.commitTimer.start();
|
||||
}
|
||||
},
|
||||
|
||||
addCSSRule: function(selectors, declarations, details) {
|
||||
if ( selectors === undefined ) { return; }
|
||||
var selectorsStr = Array.isArray(selectors)
|
||||
? selectors.join(',\n')
|
||||
: selectors;
|
||||
if ( selectorsStr.length === 0 ) { return; }
|
||||
this.addedCSSRules.push({
|
||||
selectors: selectorsStr,
|
||||
declarations,
|
||||
lazy: details && details.lazy === true,
|
||||
internal: details && details.internal === true
|
||||
});
|
||||
this.commit();
|
||||
this.triggerListeners('declarative', selectorsStr);
|
||||
},
|
||||
|
||||
removeCSSRule: function(selectors, declarations) {
|
||||
var selectorsStr = Array.isArray(selectors)
|
||||
? selectors.join(',\n')
|
||||
: selectors;
|
||||
if ( selectorsStr.length === 0 ) { return; }
|
||||
this.removedCSSRules.push({
|
||||
selectors: selectorsStr,
|
||||
declarations,
|
||||
});
|
||||
this.commit();
|
||||
},
|
||||
|
||||
addListener: function(listener) {
|
||||
if ( this.listeners.indexOf(listener) !== -1 ) { return; }
|
||||
this.listeners.push(listener);
|
||||
},
|
||||
|
||||
removeListener: function(listener) {
|
||||
var pos = this.listeners.indexOf(listener);
|
||||
if ( pos === -1 ) { return; }
|
||||
this.listeners.splice(pos, 1);
|
||||
},
|
||||
|
||||
triggerListeners: function(type, selectors) {
|
||||
var i = this.listeners.length;
|
||||
while ( i-- ) {
|
||||
this.listeners[i].onFiltersetChanged(type, selectors);
|
||||
}
|
||||
},
|
||||
|
||||
excludeNode: function(node) {
|
||||
this.excludedNodeSet.add(node);
|
||||
this.unhideNode(node);
|
||||
},
|
||||
|
||||
hideNode: function(node) {
|
||||
if ( this.excludedNodeSet.has(node) ) { return; }
|
||||
node.setAttribute(this.hideNodeId, '');
|
||||
if ( this.hideNodeStylesheet === false ) {
|
||||
this.hideNodeStylesheet = true;
|
||||
this.addCSSRule(
|
||||
'[' + this.hideNodeId + ']',
|
||||
'display: none !important;',
|
||||
{ internal: true }
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
unhideNode: function(node) {
|
||||
node.removeAttribute(this.hideNodeId);
|
||||
},
|
||||
|
||||
toggle: function(state) {
|
||||
this.userStylesheets.toggle(state);
|
||||
},
|
||||
|
||||
getAllDeclarativeSelectors_: function(all) {
|
||||
let selectors = [];
|
||||
for ( var sheet of this.userStylesheets.current ) {
|
||||
if ( all === false && this.internalRules.has(sheet) ) { continue; }
|
||||
selectors.push(
|
||||
sheet.replace(this.reOnlySelectors, ',').trim().slice(0, -1)
|
||||
);
|
||||
}
|
||||
return selectors.join(',\n');
|
||||
},
|
||||
|
||||
getFilteredElementCount: function() {
|
||||
let selectors = this.getAllDeclarativeSelectors_(true);
|
||||
return selectors.length !== 0
|
||||
? document.querySelectorAll(selectors).length
|
||||
: 0;
|
||||
},
|
||||
|
||||
getAllDeclarativeSelectors: function() {
|
||||
return this.getAllDeclarativeSelectors_(false);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
} // <<<<<<<< end of HUGE-IF-BLOCK
|
||||
|
@ -120,8 +120,8 @@ var µBlock = (function() { // jshint ignore:line
|
||||
|
||||
// read-only
|
||||
systemSettings: {
|
||||
compiledMagic: 'dhmexnfqwlom',
|
||||
selfieMagic: 'dhmexnfqwlom'
|
||||
compiledMagic: 'vrgorlgelgws',
|
||||
selfieMagic: 'vrgorlgelgws'
|
||||
},
|
||||
|
||||
restoreBackupSettings: {
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -137,6 +137,7 @@ var janitor = function() {
|
||||
) {
|
||||
api.writeOne = writeOneNoop;
|
||||
logBuffer = null;
|
||||
vAPI.messaging.broadcast({ what: 'loggerDisabled' });
|
||||
}
|
||||
if ( logBuffer !== null ) {
|
||||
vAPI.setTimeout(janitor, logBufferObsoleteAfter);
|
||||
|
@ -107,8 +107,6 @@ var onMessage = function(request, sender, callback) {
|
||||
|
||||
case 'cosmeticFiltersInjected':
|
||||
µb.cosmeticFilteringEngine.addToSelectorCache(request);
|
||||
/* falls through */
|
||||
case 'cosmeticFiltersActivated':
|
||||
// Net-based cosmetic filters are of no interest for logging purpose.
|
||||
if ( µb.logger.isEnabled() && request.type !== 'net' ) {
|
||||
µb.logCosmeticFilters(tabId);
|
||||
@ -466,9 +464,12 @@ var onMessage = function(request, sender, callback) {
|
||||
// Sync
|
||||
var µb = µBlock,
|
||||
response,
|
||||
tabId,
|
||||
pageStore;
|
||||
|
||||
if ( sender && sender.tab ) {
|
||||
pageStore = µb.pageStoreFromTabId(sender.tab.id);
|
||||
tabId = sender.tab.id;
|
||||
pageStore = µb.pageStoreFromTabId(tabId);
|
||||
}
|
||||
|
||||
switch ( request.what ) {
|
||||
@ -476,7 +477,8 @@ var onMessage = function(request, sender, callback) {
|
||||
response = {
|
||||
id: request.id,
|
||||
hash: request.hash,
|
||||
netSelectorCacheCountMax: µb.cosmeticFilteringEngine.netSelectorCacheCountMax
|
||||
netSelectorCacheCountMax:
|
||||
µb.cosmeticFilteringEngine.netSelectorCacheCountMax
|
||||
};
|
||||
if (
|
||||
µb.userSettings.collapseBlocked &&
|
||||
@ -490,22 +492,27 @@ var onMessage = function(request, sender, callback) {
|
||||
case 'retrieveContentScriptParameters':
|
||||
if ( pageStore && pageStore.getNetFilteringSwitch() ) {
|
||||
response = {
|
||||
loggerEnabled: µb.logger.isEnabled(),
|
||||
collapseBlocked: µb.userSettings.collapseBlocked,
|
||||
noCosmeticFiltering: µb.cosmeticFilteringEngine.acceptedCount === 0 || pageStore.noCosmeticFiltering === true,
|
||||
noGenericCosmeticFiltering: pageStore.noGenericCosmeticFiltering === true
|
||||
noCosmeticFiltering:
|
||||
µb.cosmeticFilteringEngine.acceptedCount === 0 ||
|
||||
pageStore.noCosmeticFiltering === true,
|
||||
noGenericCosmeticFiltering:
|
||||
pageStore.noGenericCosmeticFiltering === true
|
||||
};
|
||||
response.specificCosmeticFilters = µb.cosmeticFilteringEngine.retrieveDomainSelectors(
|
||||
request,
|
||||
response.noCosmeticFiltering
|
||||
);
|
||||
response.specificCosmeticFilters =
|
||||
µb.cosmeticFilteringEngine
|
||||
.retrieveDomainSelectors(request, sender, response);
|
||||
if ( request.isRootFrame && µb.logger.isEnabled() ) {
|
||||
µb.logCosmeticFilters(tabId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'retrieveGenericCosmeticSelectors':
|
||||
if ( pageStore && pageStore.getGenericCosmeticFilteringSwitch() ) {
|
||||
response = {
|
||||
result: µb.cosmeticFilteringEngine.retrieveGenericSelectors(request)
|
||||
result: µb.cosmeticFilteringEngine
|
||||
.retrieveGenericSelectors(request)
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
@ -338,11 +338,11 @@ RedirectEngine.prototype.toSelfie = function() {
|
||||
}
|
||||
var µb = µBlock;
|
||||
return {
|
||||
resources: µb.mapToArray(this.resources),
|
||||
resources: µb.arrayFrom(this.resources),
|
||||
rules: rules,
|
||||
ruleTypes: µb.setToArray(this.ruleTypes),
|
||||
ruleSources: µb.setToArray(this.ruleSources),
|
||||
ruleDestinations: µb.setToArray(this.ruleDestinations)
|
||||
ruleTypes: µb.arrayFrom(this.ruleTypes),
|
||||
ruleSources: µb.arrayFrom(this.ruleSources),
|
||||
ruleDestinations: µb.arrayFrom(this.ruleDestinations)
|
||||
};
|
||||
};
|
||||
|
||||
@ -359,11 +359,10 @@ RedirectEngine.prototype.fromSelfie = function(selfie) {
|
||||
}
|
||||
|
||||
// Rules.
|
||||
var µb = µBlock;
|
||||
this.rules = µb.mapFromArray(selfie.rules);
|
||||
this.ruleTypes = µb.setFromArray(selfie.ruleTypes);
|
||||
this.ruleSources = µb.setFromArray(selfie.ruleSources);
|
||||
this.ruleDestinations = µb.setFromArray(selfie.ruleDestinations);
|
||||
this.rules = new Map(selfie.rules);
|
||||
this.ruleTypes = new Set(selfie.ruleTypes);
|
||||
this.ruleSources = new Set(selfie.ruleSources);
|
||||
this.ruleDestinations = new Set(selfie.ruleDestinations);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
@ -100,10 +100,27 @@ var fromCosmeticFilter = function(details) {
|
||||
prefix = match[0],
|
||||
filter = details.rawFilter.slice(prefix.length);
|
||||
|
||||
// With low generic simple cosmetic filters, the class or id prefix
|
||||
// character is not part of the compiled data. So we must be ready to
|
||||
// look-up version of the selector without the prefix character.
|
||||
var idOrClassPrefix = filter.charAt(0),
|
||||
cssPrefixMatcher;
|
||||
if ( idOrClassPrefix === '#' ) {
|
||||
cssPrefixMatcher = '#?';
|
||||
filter = filter.slice(1);
|
||||
} else if ( idOrClassPrefix === '.' ) {
|
||||
cssPrefixMatcher = '\\.?';
|
||||
filter = filter.slice(1);
|
||||
} else {
|
||||
idOrClassPrefix = '';
|
||||
cssPrefixMatcher = '';
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/3101
|
||||
// Use `m` flag for efficient regex execution.
|
||||
var reFilter = new RegExp(
|
||||
'^\\[\\d,[^\\n]*\\\\*"' +
|
||||
cssPrefixMatcher +
|
||||
reEscapeCosmetic(filter) +
|
||||
'\\\\*"[^\\n]*\\]$',
|
||||
'gm'
|
||||
@ -154,8 +171,10 @@ var fromCosmeticFilter = function(details) {
|
||||
while ( (match = reFilter.exec(content)) !== null ) {
|
||||
fargs = JSON.parse(match[0]);
|
||||
switch ( fargs[0] ) {
|
||||
case 0:
|
||||
case 2:
|
||||
case 0: // id-based
|
||||
case 2: // class-based
|
||||
found = prefix + idOrClassPrefix + filter;
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
case 7:
|
||||
@ -169,6 +188,7 @@ var fromCosmeticFilter = function(details) {
|
||||
break;
|
||||
case 6:
|
||||
case 8:
|
||||
case 9:
|
||||
if (
|
||||
fargs[2] === '' ||
|
||||
reHostname.test(fargs[2]) === true ||
|
||||
|
@ -27,62 +27,220 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
|
||||
if (
|
||||
typeof vAPI !== 'object' ||
|
||||
vAPI.domFilterer instanceof Object === false ||
|
||||
vAPI.domWatcher instanceof Object === false
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
var loggedSelectors = vAPI.loggedSelectors || {},
|
||||
matchedSelectors = [];
|
||||
var reHasCSSCombinators = /[ >+~]/,
|
||||
reHasPseudoClass = /:+(?:after|before)$/,
|
||||
sanitizedSelectors = new Map(),
|
||||
matchProp = vAPI.matchesProp,
|
||||
simple = { dict: new Set(), str: undefined },
|
||||
complex = { dict: new Set(), str: undefined },
|
||||
procedural = { dict: new Map() },
|
||||
jobQueue = [];
|
||||
|
||||
|
||||
var evaluateSelector = function(selector) {
|
||||
var DeclarativeSimpleJob = function(node) {
|
||||
this.node = node;
|
||||
};
|
||||
DeclarativeSimpleJob.create = function(node) {
|
||||
return new DeclarativeSimpleJob(node);
|
||||
};
|
||||
DeclarativeSimpleJob.prototype.lookup = function(out) {
|
||||
if ( simple.dict.size === 0 ) { return; }
|
||||
if ( simple.str === undefined ) {
|
||||
simple.str = Array.from(simple.dict).join(',\n');
|
||||
}
|
||||
if (
|
||||
loggedSelectors.hasOwnProperty(selector) === false &&
|
||||
document.querySelector(selector) !== null
|
||||
(this.node === document || this.node[matchProp](simple.str) === false) &&
|
||||
(this.node.querySelector(simple.str) === null)
|
||||
) {
|
||||
loggedSelectors[selector] = true;
|
||||
matchedSelectors.push(selector);
|
||||
return;
|
||||
}
|
||||
for ( var selector of simple.dict ) {
|
||||
if (
|
||||
this.node !== document && this.node[matchProp](selector) ||
|
||||
this.node.querySelector(selector) !== null
|
||||
) {
|
||||
out.push(sanitizedSelectors.get(selector) || selector);
|
||||
simple.dict.delete(selector);
|
||||
simple.str = undefined;
|
||||
if ( simple.dict.size === 0 ) { return; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Simple CSS selector-based cosmetic filters.
|
||||
vAPI.domFilterer.simpleHideSelectors.entries.forEach(evaluateSelector);
|
||||
|
||||
// Complex CSS selector-based cosmetic filters.
|
||||
vAPI.domFilterer.complexHideSelectors.entries.forEach(evaluateSelector);
|
||||
|
||||
// Non-querySelector-able filters.
|
||||
vAPI.domFilterer.nqsSelectors.forEach(function(filter) {
|
||||
if ( loggedSelectors.hasOwnProperty(filter) === false ) {
|
||||
loggedSelectors[filter] = true;
|
||||
matchedSelectors.push(filter);
|
||||
var DeclarativeComplexJob = function() {
|
||||
};
|
||||
DeclarativeComplexJob.instance = null;
|
||||
DeclarativeComplexJob.create = function() {
|
||||
if ( DeclarativeComplexJob.instance === null ) {
|
||||
DeclarativeComplexJob.instance = new DeclarativeComplexJob();
|
||||
}
|
||||
});
|
||||
|
||||
// Procedural cosmetic filters.
|
||||
vAPI.domFilterer.proceduralSelectors.entries.forEach(function(pfilter) {
|
||||
if (
|
||||
loggedSelectors.hasOwnProperty(pfilter.raw) === false &&
|
||||
pfilter.exec().length !== 0
|
||||
) {
|
||||
loggedSelectors[pfilter.raw] = true;
|
||||
matchedSelectors.push(pfilter.raw);
|
||||
return DeclarativeComplexJob.instance;
|
||||
};
|
||||
DeclarativeComplexJob.prototype.lookup = function(out) {
|
||||
if ( complex.dict.size === 0 ) { return; }
|
||||
if ( complex.str === undefined ) {
|
||||
complex.str = Array.from(complex.dict).join(',\n');
|
||||
}
|
||||
});
|
||||
|
||||
vAPI.loggedSelectors = loggedSelectors;
|
||||
|
||||
if ( matchedSelectors.length ) {
|
||||
vAPI.messaging.send(
|
||||
'scriptlets',
|
||||
{
|
||||
what: 'logCosmeticFilteringData',
|
||||
frameURL: window.location.href,
|
||||
frameHostname: window.location.hostname,
|
||||
matchedSelectors: matchedSelectors
|
||||
if ( document.querySelector(complex.str) === null ) { return; }
|
||||
for ( var selector of complex.dict ) {
|
||||
if ( document.querySelector(selector) !== null ) {
|
||||
out.push(sanitizedSelectors.get(selector) || selector);
|
||||
complex.dict.delete(selector);
|
||||
complex.str = undefined;
|
||||
if ( complex.dict.size === 0 ) { return; }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var ProceduralJob = function() {
|
||||
};
|
||||
ProceduralJob.instance = null;
|
||||
ProceduralJob.create = function() {
|
||||
if ( ProceduralJob.instance === null ) {
|
||||
ProceduralJob.instance = new ProceduralJob();
|
||||
}
|
||||
return ProceduralJob.instance;
|
||||
};
|
||||
ProceduralJob.prototype.lookup = function(out) {
|
||||
for ( var entry of procedural.dict ) {
|
||||
if ( entry[1].test() ) {
|
||||
procedural.dict.delete(entry[0]);
|
||||
out.push(entry[1].raw);
|
||||
if ( procedural.dict.size === 0 ) { return; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var jobQueueTimer = new vAPI.SafeAnimationFrame(function processJobQueue() {
|
||||
console.time('dom logger/scanning for matches');
|
||||
jobQueueTimer.clear();
|
||||
var toLog = [],
|
||||
t0 = Date.now(),
|
||||
job;
|
||||
while ( (job = jobQueue.shift()) ) {
|
||||
job.lookup(toLog);
|
||||
if ( (Date.now() - t0) > 10 ) { break; }
|
||||
}
|
||||
if ( toLog.length !== 0 ) {
|
||||
vAPI.messaging.send(
|
||||
'scriptlets',
|
||||
{
|
||||
what: 'logCosmeticFilteringData',
|
||||
frameURL: window.location.href,
|
||||
frameHostname: window.location.hostname,
|
||||
matchedSelectors: toLog
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( simple.dict.size === 0 && complex.dict.size === 0 ) {
|
||||
jobQueue = [];
|
||||
}
|
||||
if ( jobQueue.length !== 0 ) {
|
||||
jobQueueTimer.start(100);
|
||||
}
|
||||
console.timeEnd('dom logger/scanning for matches');
|
||||
});
|
||||
|
||||
var handlers = {
|
||||
|
||||
onFiltersetChanged: function(type, selectors) {
|
||||
console.time('dom logger/filterset changed');
|
||||
var selector,
|
||||
sanitized;
|
||||
if ( type === 'declarative' ) {
|
||||
var simpleSizeBefore = simple.dict.size,
|
||||
complexSizeBefore = complex.dict.size;
|
||||
for ( selector of selectors.split(',\n') ) {
|
||||
if ( reHasPseudoClass.test(selector) ) {
|
||||
sanitized = selector.replace(reHasPseudoClass, '');
|
||||
sanitizedSelectors.set(sanitized, selector);
|
||||
selector = sanitized;
|
||||
}
|
||||
if ( reHasCSSCombinators.test(selector) ) {
|
||||
complex.dict.add(selector);
|
||||
complex.str = undefined;
|
||||
} else {
|
||||
simple.dict.add(selector);
|
||||
simple.str = undefined;
|
||||
}
|
||||
}
|
||||
if ( simple.dict.size !== simpleSizeBefore ) {
|
||||
jobQueue.push(DeclarativeSimpleJob.create(document));
|
||||
}
|
||||
if ( complex.dict.size !== complexSizeBefore ) {
|
||||
complex.str = Array.from(complex.dict).join(',\n');
|
||||
jobQueue.push(DeclarativeComplexJob.create());
|
||||
}
|
||||
} else if ( type === 'procedural' ) {
|
||||
for ( selector of selectors ) {
|
||||
procedural.dict.set(selector[0], selector[1]);
|
||||
}
|
||||
if ( selectors.size !== 0 ) {
|
||||
jobQueue.push(ProceduralJob.create());
|
||||
}
|
||||
}
|
||||
if ( jobQueue.length !== 0 ) {
|
||||
jobQueueTimer.start(1);
|
||||
}
|
||||
console.timeEnd('dom logger/filterset changed');
|
||||
},
|
||||
|
||||
onDOMCreated: function() {
|
||||
handlers.onFiltersetChanged(
|
||||
'declarative',
|
||||
vAPI.domFilterer.getAllDeclarativeSelectors()
|
||||
);
|
||||
handlers.onFiltersetChanged(
|
||||
'procedural',
|
||||
vAPI.domFilterer.getAllProceduralSelectors()
|
||||
);
|
||||
vAPI.domFilterer.addListener(handlers);
|
||||
},
|
||||
|
||||
onDOMChanged: function(addedNodes) {
|
||||
if ( simple.dict.size === 0 && complex.dict.size === 0 ) { return; }
|
||||
// This is to guard against runaway job queue. I suspect this could
|
||||
// occur on slower devices.
|
||||
if ( jobQueue.length <= 300 ) {
|
||||
if ( simple.dict.size !== 0 ) {
|
||||
for ( var node of addedNodes ) {
|
||||
jobQueue.push(DeclarativeSimpleJob.create(node));
|
||||
}
|
||||
}
|
||||
if ( complex.dict.size !== 0 ) {
|
||||
jobQueue.push(DeclarativeComplexJob.create());
|
||||
}
|
||||
if ( procedural.dict.size !== 0 ) {
|
||||
jobQueue.push(ProceduralJob.create());
|
||||
}
|
||||
}
|
||||
if ( jobQueue.length !== 0 ) {
|
||||
jobQueueTimer.start(100);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var onMessage = function(msg) {
|
||||
if ( msg.what === 'loggerDisabled' ) {
|
||||
jobQueueTimer.clear();
|
||||
vAPI.domFilterer.removeListener(handlers);
|
||||
vAPI.domWatcher.removeListener(handlers);
|
||||
vAPI.messaging.removeChannelListener('domLogger', onMessage);
|
||||
}
|
||||
};
|
||||
vAPI.messaging.addChannelListener('domLogger', onMessage);
|
||||
|
||||
vAPI.domWatcher.addListener(handlers);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2015-2016 Raymond Hill
|
||||
Copyright (C) 2015-2017 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
|
||||
@ -23,20 +23,6 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(function() {
|
||||
if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var elems = [];
|
||||
try {
|
||||
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
|
||||
} catch (e) {
|
||||
}
|
||||
var i = elems.length;
|
||||
while ( i-- ) {
|
||||
vAPI.domFilterer.showNode(elems[i]);
|
||||
}
|
||||
|
||||
vAPI.domFilterer.toggleOff();
|
||||
})();
|
||||
if ( typeof vAPI === 'object' && vAPI.domFilterer ) {
|
||||
vAPI.domFilterer.toggle(false);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2015-2016 Raymond Hill
|
||||
Copyright (C) 2015-2017 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
|
||||
@ -23,20 +23,6 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(function() {
|
||||
if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var elems = [];
|
||||
try {
|
||||
elems = document.querySelectorAll('[' + vAPI.domFilterer.hiddenId + ']');
|
||||
} catch (e) {
|
||||
}
|
||||
var i = elems.length;
|
||||
while ( i-- ) {
|
||||
vAPI.domFilterer.unshowNode(elems[i]);
|
||||
}
|
||||
|
||||
vAPI.domFilterer.toggleOn();
|
||||
})();
|
||||
if ( typeof vAPI === 'object' && vAPI.domFilterer ) {
|
||||
vAPI.domFilterer.toggle(true);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2015-2016 Raymond Hill
|
||||
Copyright (C) 2015-2017 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
|
||||
@ -24,24 +24,14 @@
|
||||
/******************************************************************************/
|
||||
|
||||
(function() {
|
||||
if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var xpr = document.evaluate(
|
||||
'count(//*[@' + vAPI.domFilterer.hiddenId + '])',
|
||||
document,
|
||||
null,
|
||||
XPathResult.NUMBER_TYPE,
|
||||
null
|
||||
);
|
||||
if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; }
|
||||
|
||||
vAPI.messaging.send(
|
||||
'scriptlets',
|
||||
{
|
||||
what: 'cosmeticallyFilteredElementCount',
|
||||
pageURL: window.location.href,
|
||||
filteredElementCount: xpr && xpr.numberValue || 0
|
||||
filteredElementCount: vAPI.domFilterer.getFilteredElementCount()
|
||||
}
|
||||
);
|
||||
})();
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2015-2016 Raymond Hill
|
||||
Copyright (C) 2015-2017 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
|
||||
@ -140,11 +140,16 @@ var cssEscape = (function(/*root*/) {
|
||||
// Highlighter-related
|
||||
var svgRoot = null;
|
||||
var pickerRoot = null;
|
||||
var highlightedElementLists = [ [], [], [] ];
|
||||
|
||||
var nodeToIdMap = new WeakMap(); // No need to iterate
|
||||
var nodeToCosmeticFilterMap = new WeakMap();
|
||||
var toggledNodes = new Map();
|
||||
|
||||
var blueNodes = [];
|
||||
var roRedNodes = new Map(); // node => current cosmetic filter
|
||||
var rwRedNodes = new Set(); // node => new cosmetic filter (toggle node)
|
||||
//var roGreenNodes = new Map(); // node => current exception cosmetic filter (can't toggle)
|
||||
var rwGreenNodes = new Set(); // node => new exception cosmetic filter (toggle filter)
|
||||
|
||||
var reHasCSSCombinators = /[ >+~]/;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -224,7 +229,7 @@ var domLayout = (function() {
|
||||
this.lvl = 0;
|
||||
this.sel = 'body';
|
||||
this.cnt = 0;
|
||||
this.filter = nodeToCosmeticFilterMap.get(document.body);
|
||||
this.filter = roRedNodes.get(document.body);
|
||||
};
|
||||
|
||||
var DomNode = function(node, level) {
|
||||
@ -232,7 +237,7 @@ var domLayout = (function() {
|
||||
this.lvl = level;
|
||||
this.sel = selectorFromNode(node);
|
||||
this.cnt = 0;
|
||||
this.filter = nodeToCosmeticFilterMap.get(node);
|
||||
this.filter = roRedNodes.get(node);
|
||||
};
|
||||
|
||||
var domNodeFactory = function(level, node) {
|
||||
@ -687,20 +692,27 @@ var cosmeticFilterMapper = (function() {
|
||||
}
|
||||
|
||||
var nodesFromStyleTag = function(rootNode) {
|
||||
var filterMap = nodeToCosmeticFilterMap,
|
||||
var filterMap = roRedNodes,
|
||||
selectors, selector,
|
||||
nodes, node,
|
||||
i, j;
|
||||
|
||||
// CSS-based selectors: simple one.
|
||||
selectors = vAPI.domFilterer.simpleHideSelectors.entries;
|
||||
// Declarative selectors.
|
||||
selectors = vAPI.domFilterer.getAllDeclarativeSelectors().split(',\n');
|
||||
i = selectors.length;
|
||||
while ( i-- ) {
|
||||
selector = selectors[i];
|
||||
if ( filterMap.has(rootNode) === false && rootNode[matchesFnName](selector) ) {
|
||||
filterMap.set(rootNode, selector);
|
||||
if ( reHasCSSCombinators.test(selector) ) {
|
||||
nodes = document.querySelectorAll(selector);
|
||||
} else {
|
||||
if (
|
||||
filterMap.has(rootNode) === false &&
|
||||
rootNode[matchesFnName](selector)
|
||||
) {
|
||||
filterMap.set(rootNode, selector);
|
||||
}
|
||||
nodes = rootNode.querySelectorAll(selector);
|
||||
}
|
||||
nodes = rootNode.querySelectorAll(selector);
|
||||
j = nodes.length;
|
||||
while ( j-- ) {
|
||||
node = nodes[j];
|
||||
@ -710,42 +722,31 @@ var cosmeticFilterMapper = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// CSS-based selectors: complex one (must query from doc root).
|
||||
selectors = vAPI.domFilterer.complexHideSelectors.entries;
|
||||
i = selectors.length;
|
||||
while ( i-- ) {
|
||||
selector = selectors[i];
|
||||
nodes = document.querySelectorAll(selector);
|
||||
// Procedural selectors.
|
||||
selectors = vAPI.domFilterer.getAllProceduralSelectors();
|
||||
for ( var entry of selectors ) {
|
||||
nodes = entry[1].exec();
|
||||
j = nodes.length;
|
||||
while ( j-- ) {
|
||||
node = nodes[j];
|
||||
if ( filterMap.has(node) === false ) {
|
||||
filterMap.set(node, selector);
|
||||
filterMap.set(node, entry[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Non-CSS selectors.
|
||||
var runJobCallback = function(node, pfilter) {
|
||||
if ( filterMap.has(node) === false ) {
|
||||
filterMap.set(node, pfilter.raw);
|
||||
}
|
||||
};
|
||||
vAPI.domFilterer.proceduralSelectors.forEachNode(runJobCallback);
|
||||
};
|
||||
|
||||
var incremental = function(rootNode) {
|
||||
vAPI.domFilterer.userCSS.toggle(false);
|
||||
vAPI.domFilterer.toggle(false);
|
||||
nodesFromStyleTag(rootNode);
|
||||
};
|
||||
|
||||
var reset = function() {
|
||||
nodeToCosmeticFilterMap = new WeakMap();
|
||||
roRedNodes = new Map();
|
||||
incremental(document.documentElement);
|
||||
};
|
||||
|
||||
var shutdown = function() {
|
||||
vAPI.domFilterer.userCSS.toggle(true);
|
||||
vAPI.domFilterer.toggle(true);
|
||||
};
|
||||
|
||||
return {
|
||||
@ -824,55 +825,96 @@ var getSvgRootChildren = function() {
|
||||
var highlightElements = function(scrollTo) {
|
||||
var wv = pickerRoot.contentWindow.innerWidth;
|
||||
var hv = pickerRoot.contentWindow.innerHeight;
|
||||
var ocean = ['M0 0h' + wv + 'v' + hv + 'h-' + wv, 'z'], islands;
|
||||
var elems, elem, rect, poly;
|
||||
var islands;
|
||||
var elem, rect, poly;
|
||||
var xl, xr, yt, yb, w, h, ws;
|
||||
var xlu = Number.MAX_VALUE, xru = 0, ytu = Number.MAX_VALUE, ybu = 0;
|
||||
var lists = highlightedElementLists;
|
||||
var svgRootChildren = getSvgRootChildren();
|
||||
|
||||
for ( var i = 0; i < lists.length; i++ ) {
|
||||
elems = lists[i];
|
||||
islands = [];
|
||||
for ( var j = 0; j < elems.length; j++ ) {
|
||||
elem = elems[j];
|
||||
if ( elem === pickerRoot ) {
|
||||
continue;
|
||||
}
|
||||
if ( typeof elem.getBoundingClientRect !== 'function' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rect = elem.getBoundingClientRect();
|
||||
xl = rect.left;
|
||||
xr = rect.right;
|
||||
w = rect.width;
|
||||
yt = rect.top;
|
||||
yb = rect.bottom;
|
||||
h = rect.height;
|
||||
|
||||
ws = w.toFixed(1);
|
||||
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
|
||||
'h' + ws +
|
||||
'v' + h.toFixed(1) +
|
||||
'h-' + ws +
|
||||
'z';
|
||||
ocean.push(poly);
|
||||
islands.push(poly);
|
||||
|
||||
if ( !scrollTo ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( xl < xlu ) { xlu = xl; }
|
||||
if ( xr > xru ) { xru = xr; }
|
||||
if ( yt < ytu ) { ytu = yt; }
|
||||
if ( yb > ybu ) { ybu = yb; }
|
||||
}
|
||||
svgRootChildren[i+1].setAttribute('d', islands.join('') || 'M0 0');
|
||||
islands = [];
|
||||
for ( elem of rwRedNodes.keys() ) {
|
||||
if ( elem === pickerRoot ) { continue; }
|
||||
if ( rwGreenNodes.has(elem) ) { continue; }
|
||||
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
|
||||
rect = elem.getBoundingClientRect();
|
||||
xl = rect.left;
|
||||
xr = rect.right;
|
||||
w = rect.width;
|
||||
yt = rect.top;
|
||||
yb = rect.bottom;
|
||||
h = rect.height;
|
||||
ws = w.toFixed(1);
|
||||
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
|
||||
'h' + ws +
|
||||
'v' + h.toFixed(1) +
|
||||
'h-' + ws +
|
||||
'z';
|
||||
islands.push(poly);
|
||||
}
|
||||
svgRootChildren[0].setAttribute('d', islands.join('') || 'M0 0');
|
||||
|
||||
svgRoot.firstElementChild.setAttribute('d', ocean.join(''));
|
||||
islands = [];
|
||||
for ( elem of rwGreenNodes ) {
|
||||
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
|
||||
rect = elem.getBoundingClientRect();
|
||||
xl = rect.left;
|
||||
xr = rect.right;
|
||||
w = rect.width;
|
||||
yt = rect.top;
|
||||
yb = rect.bottom;
|
||||
h = rect.height;
|
||||
ws = w.toFixed(1);
|
||||
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
|
||||
'h' + ws +
|
||||
'v' + h.toFixed(1) +
|
||||
'h-' + ws +
|
||||
'z';
|
||||
islands.push(poly);
|
||||
}
|
||||
svgRootChildren[1].setAttribute('d', islands.join('') || 'M0 0');
|
||||
|
||||
islands = [];
|
||||
for ( elem of roRedNodes.keys() ) {
|
||||
if ( elem === pickerRoot ) { continue; }
|
||||
if ( rwGreenNodes.has(elem) ) { continue; }
|
||||
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
|
||||
rect = elem.getBoundingClientRect();
|
||||
xl = rect.left;
|
||||
xr = rect.right;
|
||||
w = rect.width;
|
||||
yt = rect.top;
|
||||
yb = rect.bottom;
|
||||
h = rect.height;
|
||||
ws = w.toFixed(1);
|
||||
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
|
||||
'h' + ws +
|
||||
'v' + h.toFixed(1) +
|
||||
'h-' + ws +
|
||||
'z';
|
||||
islands.push(poly);
|
||||
}
|
||||
svgRootChildren[2].setAttribute('d', islands.join('') || 'M0 0');
|
||||
|
||||
islands = [];
|
||||
for ( elem of blueNodes ) {
|
||||
if ( elem === pickerRoot ) { continue; }
|
||||
if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
|
||||
rect = elem.getBoundingClientRect();
|
||||
xl = rect.left;
|
||||
xr = rect.right;
|
||||
w = rect.width;
|
||||
yt = rect.top;
|
||||
yb = rect.bottom;
|
||||
h = rect.height;
|
||||
ws = w.toFixed(1);
|
||||
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
|
||||
'h' + ws +
|
||||
'v' + h.toFixed(1) +
|
||||
'h-' + ws +
|
||||
'z';
|
||||
islands.push(poly);
|
||||
}
|
||||
svgRootChildren[3].setAttribute('d', islands.join('') || 'M0 0');
|
||||
|
||||
if ( !scrollTo ) {
|
||||
return;
|
||||
@ -913,34 +955,12 @@ var onScrolled = function() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var resetToggledNodes = function() {
|
||||
for ( var entry of toggledNodes ) {
|
||||
if ( entry[1].show ) {
|
||||
showNode(entry[0], entry[1].v1, entry[1].v2);
|
||||
} else {
|
||||
hideNode(entry[0]);
|
||||
}
|
||||
}
|
||||
toggledNodes.clear();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var forgetToggledNodes = function() {
|
||||
toggledNodes.clear();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var selectNodes = function(selector, nid) {
|
||||
var nodes = elementsFromSelector(selector);
|
||||
if ( nid === '' ) {
|
||||
return nodes;
|
||||
}
|
||||
var i = nodes.length;
|
||||
while ( i-- ) {
|
||||
if ( nodeToIdMap.get(nodes[i]) === nid ) {
|
||||
return [nodes[i]];
|
||||
if ( nid === '' ) { return nodes; }
|
||||
for ( var node of nodes ) {
|
||||
if ( nodeToIdMap.get(node) === nid ) {
|
||||
return [ node ];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
@ -950,84 +970,37 @@ var selectNodes = function(selector, nid) {
|
||||
|
||||
var shutdown = function() {
|
||||
cosmeticFilterMapper.shutdown();
|
||||
resetToggledNodes();
|
||||
domLayout.shutdown();
|
||||
vAPI.messaging.removeAllChannelListeners('domInspector');
|
||||
window.removeEventListener('scroll', onScrolled, true);
|
||||
document.documentElement.removeChild(pickerRoot);
|
||||
pickerRoot = svgRoot = null;
|
||||
highlightedElementLists = [ [], [], [] ];
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// original, target = what to do
|
||||
// any, any = restore saved display property
|
||||
// any, hidden = set display to `none`, remember original state
|
||||
// hidden, any = remove display property, don't remember original state
|
||||
// hidden, hidden = set display to `none`
|
||||
|
||||
var toggleNodes = function(nodes, originalState, targetState) {
|
||||
var i = nodes.length;
|
||||
if ( i === 0 ) {
|
||||
return;
|
||||
}
|
||||
var node, details;
|
||||
while ( i-- ) {
|
||||
node = nodes[i];
|
||||
// originally visible node
|
||||
if ( originalState ) {
|
||||
// unhide visible node
|
||||
if ( targetState ) {
|
||||
details = toggledNodes.get(node) || {};
|
||||
showNode(node, details.v1, details.v2);
|
||||
toggledNodes.delete(node);
|
||||
}
|
||||
// hide visible node
|
||||
else {
|
||||
toggledNodes.set(node, {
|
||||
show: true,
|
||||
v1: node.style.getPropertyValue('display') || '',
|
||||
v2: node.style.getPropertyPriority('display') || ''
|
||||
});
|
||||
hideNode(node);
|
||||
}
|
||||
var toggleExceptions = function(nodes, targetState) {
|
||||
for ( var node of nodes ) {
|
||||
if ( targetState ) {
|
||||
rwGreenNodes.add(node);
|
||||
} else {
|
||||
rwGreenNodes.delete(node);
|
||||
}
|
||||
// originally hidden node
|
||||
else {
|
||||
// show hidden node
|
||||
if ( targetState ) {
|
||||
toggledNodes.set(node, { show: false });
|
||||
showNode(node, 'initial', 'important');
|
||||
}
|
||||
// hide hidden node
|
||||
else {
|
||||
hideNode(node);
|
||||
toggledNodes.delete(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var toggleFilter = function(nodes, targetState) {
|
||||
for ( var node of nodes ) {
|
||||
if ( targetState ) {
|
||||
rwRedNodes.delete(node);
|
||||
} else {
|
||||
rwRedNodes.add(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// https://www.youtube.com/watch?v=L5jRewnxSBY
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var showNode = function(node, v1, v2) {
|
||||
vAPI.domFilterer.showNode(node);
|
||||
if ( !v1 ) {
|
||||
node.style.removeProperty('display');
|
||||
} else {
|
||||
node.style.setProperty('display', v1, v2);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var hideNode = function(node) {
|
||||
vAPI.domFilterer.unshowNode(node);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
@ -1036,11 +1009,6 @@ var onMessage = function(request) {
|
||||
|
||||
switch ( request.what ) {
|
||||
case 'commitFilters':
|
||||
resetToggledNodes();
|
||||
toggleNodes(selectNodes(request.hide, ''), true, false);
|
||||
toggleNodes(selectNodes(request.unhide, ''), false, true);
|
||||
forgetToggledNodes();
|
||||
highlightedElementLists = [ [], [], [] ];
|
||||
highlightElements();
|
||||
break;
|
||||
|
||||
@ -1053,44 +1021,38 @@ var onMessage = function(request) {
|
||||
break;
|
||||
|
||||
case 'highlightMode':
|
||||
svgRoot.classList.toggle('invert', request.invert);
|
||||
//svgRoot.classList.toggle('invert', request.invert);
|
||||
break;
|
||||
|
||||
case 'highlightOne':
|
||||
highlightedElementLists[0] = selectNodes(request.selector, request.nid);
|
||||
blueNodes = selectNodes(request.selector, request.nid);
|
||||
highlightElements(request.scrollTo);
|
||||
break;
|
||||
|
||||
case 'resetToggledNodes':
|
||||
resetToggledNodes();
|
||||
break;
|
||||
|
||||
case 'showCommitted':
|
||||
resetToggledNodes();
|
||||
highlightedElementLists[0] = [];
|
||||
highlightedElementLists[1] = selectNodes(request.hide, '');
|
||||
highlightedElementLists[2] = selectNodes(request.unhide, '');
|
||||
toggleNodes(highlightedElementLists[2], false, true);
|
||||
blueNodes = [];
|
||||
// TODO: show only the new filters and exceptions.
|
||||
highlightElements(true);
|
||||
break;
|
||||
|
||||
case 'showInteractive':
|
||||
resetToggledNodes();
|
||||
toggleNodes(selectNodes(request.hide, ''), true, false);
|
||||
toggleNodes(selectNodes(request.unhide, ''), false, true);
|
||||
highlightedElementLists = [ [], [], [] ];
|
||||
blueNodes = [];
|
||||
highlightElements();
|
||||
break;
|
||||
|
||||
case 'toggleFilter':
|
||||
highlightedElementLists[0] = selectNodes(request.filter, request.nid);
|
||||
toggleNodes(highlightedElementLists[0], request.original, request.target);
|
||||
toggleExceptions(
|
||||
selectNodes(request.filter, request.nid),
|
||||
request.target
|
||||
);
|
||||
highlightElements(true);
|
||||
break;
|
||||
|
||||
case 'toggleNodes':
|
||||
highlightedElementLists[0] = selectNodes(request.selector, request.nid);
|
||||
toggleNodes(highlightedElementLists[0], request.original, request.target);
|
||||
toggleFilter(
|
||||
selectNodes(request.selector, request.nid),
|
||||
request.target
|
||||
);
|
||||
highlightElements(true);
|
||||
break;
|
||||
|
||||
@ -1149,22 +1111,22 @@ pickerRoot.onload = function() {
|
||||
'top: 0;',
|
||||
'width: 100%;',
|
||||
'}',
|
||||
'svg > path:first-child {',
|
||||
'fill: rgba(0,0,0,0.75);',
|
||||
'fill-rule: evenodd;',
|
||||
'svg > path:nth-of-type(1) {',
|
||||
'fill: rgba(255,0,0,0.2);',
|
||||
'stroke: #F00;',
|
||||
'}',
|
||||
'svg > path:nth-of-type(2) {',
|
||||
'fill: rgba(0,0,255,0.1);',
|
||||
'stroke: #FFF;',
|
||||
'stroke-width: 0.5px;',
|
||||
'fill: rgba(0,255,0,0.2);',
|
||||
'stroke: #0F0;',
|
||||
'}',
|
||||
'svg > path:nth-of-type(3) {',
|
||||
'fill: rgba(255,0,0,0.2);',
|
||||
'stroke: #F00;',
|
||||
'}',
|
||||
'svg > path:nth-of-type(4) {',
|
||||
'fill: rgba(0,255,0,0.2);',
|
||||
'stroke: #0F0;',
|
||||
'fill: rgba(0,0,255,0.1);',
|
||||
'stroke: #FFF;',
|
||||
'stroke-width: 0.5px;',
|
||||
'}',
|
||||
''
|
||||
].join('\n');
|
||||
@ -1179,8 +1141,8 @@ pickerRoot.onload = function() {
|
||||
|
||||
window.addEventListener('scroll', onScrolled, true);
|
||||
|
||||
highlightElements();
|
||||
cosmeticFilterMapper.reset();
|
||||
highlightElements();
|
||||
|
||||
vAPI.messaging.addChannelListener('domInspector', onMessage);
|
||||
};
|
||||
|
@ -118,12 +118,11 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if ( typeof vAPI !== 'object' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't run in frames
|
||||
if ( window.top !== window ) {
|
||||
if (
|
||||
window.top !== window ||
|
||||
typeof vAPI !== 'object' ||
|
||||
vAPI.domFilterer instanceof Object === false
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -131,8 +130,8 @@ var pickerRoot = document.getElementById(vAPI.sessionId);
|
||||
if ( pickerRoot ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pickerBody = null;
|
||||
var pickerStyle = null;
|
||||
var svgOcean = null;
|
||||
var svgIslands = null;
|
||||
var svgRoot = null;
|
||||
@ -1397,13 +1396,12 @@ var stopPicker = function() {
|
||||
candidateElements = [];
|
||||
bestCandidateFilter = null;
|
||||
|
||||
if ( pickerRoot === null ) {
|
||||
return;
|
||||
}
|
||||
if ( pickerRoot === null ) { return; }
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/2060
|
||||
if ( vAPI.userCSS ) {
|
||||
vAPI.userCSS.remove(pickerStyle.textContent);
|
||||
if ( vAPI.domFilterer instanceof Object ) {
|
||||
vAPI.domFilterer.removeCSSRule(pickerCSSSelector1, pickerCSSDeclaration1);
|
||||
vAPI.domFilterer.removeCSSRule(pickerCSSSelector2, pickerCSSDeclaration2);
|
||||
}
|
||||
|
||||
window.removeEventListener('scroll', onScrolled, true);
|
||||
@ -1414,7 +1412,6 @@ var stopPicker = function() {
|
||||
svgRoot.removeEventListener('click', onSvgClicked);
|
||||
svgRoot.removeEventListener('touchstart', onSvgTouchStartStop);
|
||||
svgRoot.removeEventListener('touchend', onSvgTouchStartStop);
|
||||
pickerStyle.parentNode.removeChild(pickerStyle);
|
||||
pickerRoot.parentNode.removeChild(pickerRoot);
|
||||
pickerRoot.removeEventListener('load', stopPicker);
|
||||
pickerRoot =
|
||||
@ -1548,50 +1545,51 @@ var bootstrapPicker = function() {
|
||||
|
||||
pickerRoot = document.createElement('iframe');
|
||||
pickerRoot.id = vAPI.sessionId;
|
||||
pickerRoot.style.cssText = [
|
||||
'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;');
|
||||
|
||||
var pickerCSSSelector1 = '#' + pickerRoot.id;
|
||||
var pickerCSSDeclaration1 = [
|
||||
'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;');
|
||||
var pickerCSSSelector2 = '[' + pickerRoot.id + '-clickblind]';
|
||||
var pickerCSSDeclaration2 = 'pointer-events: none !important;';
|
||||
|
||||
|
||||
pickerRoot.style.cssText = pickerCSSDeclaration1;
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/1529
|
||||
// In addition to inline styles, harden the element picker styles by using
|
||||
// a dedicated style tag.
|
||||
pickerStyle = document.createElement('style');
|
||||
pickerStyle.textContent = [
|
||||
'#' + pickerRoot.id + ' {',
|
||||
pickerRoot.style.cssText,
|
||||
'}',
|
||||
'[' + pickerRoot.id + '-clickblind] {',
|
||||
'pointer-events: none !important;',
|
||||
'}',
|
||||
''
|
||||
].join('\n');
|
||||
document.documentElement.appendChild(pickerStyle);
|
||||
// In addition to inline styles, harden the element picker styles by using
|
||||
// dedicated CSS rules.
|
||||
vAPI.domFilterer.addCSSRule(
|
||||
pickerCSSSelector1,
|
||||
pickerCSSDeclaration1,
|
||||
{ internal: true }
|
||||
);
|
||||
vAPI.domFilterer.addCSSRule(
|
||||
pickerCSSSelector2,
|
||||
pickerCSSDeclaration2,
|
||||
{ internal: true }
|
||||
);
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/2060
|
||||
if ( vAPI.domFilterer ) {
|
||||
pickerRoot[vAPI.domFilterer.getExcludeId()] = true;
|
||||
}
|
||||
if ( vAPI.userCSS ) {
|
||||
vAPI.userCSS.add(pickerStyle.textContent);
|
||||
}
|
||||
vAPI.domFilterer.excludeNode(pickerRoot);
|
||||
|
||||
pickerRoot.addEventListener('load', bootstrapPicker);
|
||||
document.documentElement.appendChild(pickerRoot);
|
||||
|
@ -1137,12 +1137,12 @@ FilterHostnameDict.prototype.logData = function() {
|
||||
};
|
||||
|
||||
FilterHostnameDict.prototype.compile = function() {
|
||||
return [ this.fid, µb.setToArray(this.dict) ];
|
||||
return [ this.fid, µb.arrayFrom(this.dict) ];
|
||||
};
|
||||
|
||||
FilterHostnameDict.load = function(args) {
|
||||
var f = new FilterHostnameDict();
|
||||
f.dict = µb.setFromArray(args[1]);
|
||||
f.dict = new Set(args[1]);
|
||||
return f;
|
||||
};
|
||||
|
||||
@ -2006,8 +2006,8 @@ FilterContainer.prototype.freeze = function() {
|
||||
this.fdataLast = null;
|
||||
this.filterLast = null;
|
||||
this.frozen = true;
|
||||
//console.log(JSON.stringify(Array.from(filterClassHistogram)));
|
||||
//this.tokenHistogram = new Map(Array.from(this.tokenHistogram).sort(function(a, b) {
|
||||
//console.log(JSON.stringify(µb.arrayFrom(filterClassHistogram)));
|
||||
//this.tokenHistogram = new Map(µb.arrayFrom(this.tokenHistogram).sort(function(a, b) {
|
||||
// return a[0].localeCompare(b[0]) || (b[1] - a[1]);
|
||||
//}));
|
||||
};
|
||||
|
@ -211,7 +211,7 @@
|
||||
this.removeFilterList(oldKeys[i]);
|
||||
}
|
||||
}
|
||||
newKeys = this.setToArray(newSet);
|
||||
newKeys = this.arrayFrom(newSet);
|
||||
var bin = {
|
||||
selectedFilterLists: newKeys,
|
||||
remoteBlacklists: this.oldDataFromNewListKeys(newKeys)
|
||||
@ -342,10 +342,10 @@
|
||||
}
|
||||
selectedListKeySet.add(assetKey);
|
||||
}
|
||||
externalLists = this.setToArray(importedSet).sort().join('\n');
|
||||
externalLists = this.arrayFrom(importedSet).sort().join('\n');
|
||||
}
|
||||
|
||||
var result = this.setToArray(selectedListKeySet);
|
||||
var result = this.arrayFrom(selectedListKeySet);
|
||||
if ( externalLists !== this.userSettings.externalLists ) {
|
||||
this.userSettings.externalLists = externalLists;
|
||||
vAPI.storage.set({ externalLists: externalLists });
|
||||
@ -371,7 +371,7 @@
|
||||
}
|
||||
out.add(location);
|
||||
}
|
||||
return this.setToArray(out);
|
||||
return this.arrayFrom(out);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -320,34 +320,16 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µBlock.mapToArray = typeof Array.from === 'function'
|
||||
µBlock.arrayFrom = typeof Array.from === 'function'
|
||||
? Array.from
|
||||
: function(map) {
|
||||
var out = [];
|
||||
for ( var entry of map ) {
|
||||
out.push(entry);
|
||||
: function(iterable) {
|
||||
var out = [], i = 0;
|
||||
for ( var value of iterable ) {
|
||||
out[i++] = value;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
µBlock.mapFromArray = function(arr) {
|
||||
return new Map(arr);
|
||||
};
|
||||
|
||||
µBlock.setToArray = typeof Array.from === 'function'
|
||||
? Array.from
|
||||
: function(dict) {
|
||||
var out = [];
|
||||
for ( var value of dict ) {
|
||||
out.push(value);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
µBlock.setFromArray = function(arr) {
|
||||
return new Set(arr);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µBlock.openNewTab = function(details) {
|
||||
@ -376,3 +358,41 @@
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µBlock.MRUCache = function(size) {
|
||||
this.size = size;
|
||||
this.array = [];
|
||||
this.map = new Map();
|
||||
};
|
||||
|
||||
µBlock.MRUCache.prototype = {
|
||||
add: function(key, value) {
|
||||
var found = this.map.has(key);
|
||||
this.map.set(key, value);
|
||||
if ( !found ) {
|
||||
if ( this.array.length === this.size ) {
|
||||
this.map.delete(this.array.pop());
|
||||
}
|
||||
this.array.unshift(key);
|
||||
}
|
||||
},
|
||||
remove: function(key) {
|
||||
if ( this.map.has(key) ) {
|
||||
this.array.splice(this.array.indexOf(key), 1);
|
||||
}
|
||||
},
|
||||
lookup: function(key) {
|
||||
var value = this.map.get(key);
|
||||
if ( value !== undefined && this.array[0] !== key ) {
|
||||
this.array.splice(this.array.indexOf(key), 1);
|
||||
this.array.unshift(key);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
reset: function() {
|
||||
this.array = [];
|
||||
this.map.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -21,6 +21,7 @@ mv $DES/img/icon_128.png $DES/icon.png
|
||||
cp platform/firefox/css/* $DES/css/
|
||||
cp platform/firefox/polyfill.js $DES/js/
|
||||
cp platform/firefox/vapi-*.js $DES/js/
|
||||
cp platform/webext/vapi-usercss.js $DES/js/
|
||||
cp platform/firefox/bootstrap.js $DES/
|
||||
cp platform/firefox/processScript.js $DES/
|
||||
cp platform/firefox/frame*.js $DES/
|
||||
|
Loading…
Reference in New Issue
Block a user