1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-10-04 08:37:11 +02:00
This commit is contained in:
gorhill 2017-10-21 13:43:46 -04:00
parent 7f2ee32f25
commit 6112a68faf
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
24 changed files with 2648 additions and 2042 deletions

View File

@ -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
},

View File

@ -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;
}

View File

@ -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;
}

View 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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -137,6 +137,7 @@ var janitor = function() {
) {
api.writeOne = writeOneNoop;
logBuffer = null;
vAPI.messaging.broadcast({ what: 'loggerDisabled' });
}
if ( logBuffer !== null ) {
vAPI.setTimeout(janitor, logBufferObsoleteAfter);

View File

@ -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;

View File

@ -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;
};

View File

@ -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 ||

View File

@ -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);
/******************************************************************************/

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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()
}
);
})();

View File

@ -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);
};

View File

@ -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);

View File

@ -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]);
//}));
};

View File

@ -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);
};
/******************************************************************************/

View File

@ -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();
}
};
/******************************************************************************/

View File

@ -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/