1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-10-04 16:47:15 +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": [ "content_scripts": [
{ {
"matches": ["http://*/*", "https://*/*"], "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", "run_at": "document_start",
"all_frames": true "all_frames": true
}, },

View File

@ -853,22 +853,17 @@ vAPI.messaging.onPortMessage = (function() {
if ( supportsUserStylesheets ) { if ( supportsUserStylesheets ) {
details.cssOrigin = 'user'; details.cssOrigin = 'user';
} }
var fn;
if ( msg.add ) { if ( msg.add ) {
details.runAt = 'document_start'; details.runAt = 'document_start';
fn = chrome.tabs.insertCSS;
} else {
fn = chrome.tabs.removeCSS;
} }
var css = msg.css; var cssText;
if ( typeof css === 'string' ) { for ( cssText of msg.add ) {
details.code = css; details.code = cssText;
fn(tabId, details); chrome.tabs.insertCSS(tabId, details);
return;
} }
for ( var i = 0, n = css.length; i < n; i++ ) { for ( cssText of msg.remove ) {
details.code = css[i]; details.code = cssText;
fn(tabId, details); chrome.tabs.removeCSS(tabId, details);
} }
break; break;
} }

View File

@ -171,14 +171,15 @@ vAPI.messaging = {
portPoller: function() { portPoller: function() {
this.portTimer = null; this.portTimer = null;
if ( this.port !== null ) { if (
if ( this.channelCount !== 0 || this.pendingCount !== 0 ) { this.port !== null &&
this.channelCount === 0 &&
this.pendingCount === 0
) {
return this.destroyPort();
}
this.portTimer = vAPI.setTimeout(this.portPollerCallback, this.portTimerDelay); this.portTimer = vAPI.setTimeout(this.portPollerCallback, this.portTimerDelay);
this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000); this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000);
return;
}
}
this.destroyPort();
}, },
portPollerCallback: null, portPollerCallback: null,
@ -324,15 +325,12 @@ vAPI.messaging = {
sendToChannelListeners: function(channelName, msg) { sendToChannelListeners: function(channelName, msg) {
var listeners = this.channels[channelName]; var listeners = this.channels[channelName];
if ( listeners === undefined ) { if ( listeners === undefined ) { return; }
return; listeners = listeners.slice(0);
}
var response; var response;
for ( var i = 0, n = listeners.length; i < n; i++ ) { for ( var listener of listeners ) {
response = listeners[i](msg); response = listener(msg);
if ( response !== undefined ) { if ( response !== undefined ) { break; }
break;
}
} }
return response; 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); let sandbox = this.initContentScripts(win, true);
try { try {
lss(this.contentBaseURI + 'vapi-client.js', sandbox); lss(this.contentBaseURI + 'vapi-client.js', sandbox);
lss(this.contentBaseURI + 'vapi-usercss.js', sandbox);
lss(this.contentBaseURI + 'contentscript.js', sandbox); lss(this.contentBaseURI + 'contentscript.js', sandbox);
} catch (ex) { } catch (ex) {
//console.exception(ex.msg, ex.stack); //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 = { vAPI.messaging = {
channels: Object.create(null), channels: Object.create(null),
channelCount: 0, channelCount: 0,
@ -276,6 +306,10 @@ vAPI.messaging = {
}, },
send: function(channelName, message, callback) { 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); this.sendTo(channelName, message, undefined, undefined, callback);
}, },
@ -358,15 +392,12 @@ vAPI.messaging = {
sendToChannelListeners: function(channelName, msg) { sendToChannelListeners: function(channelName, msg) {
var listeners = this.channels[channelName]; var listeners = this.channels[channelName];
if ( listeners === undefined ) { if ( listeners === undefined ) { return; }
return; listeners = listeners.slice(0);
}
var response; var response;
for ( var i = 0, n = listeners.length; i < n; i++ ) { for ( var listener of listeners ) {
response = listeners[i](msg); response = listener(msg);
if ( response !== undefined ) { if ( response !== undefined ) { break; }
break;
}
} }
return response; 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://bugzilla.mozilla.org/show_bug.cgi?id=444165
// https://github.com/gorhill/uBlock/issues/2256 // https://github.com/gorhill/uBlock/issues/2256
// Not the prettiest solution, but that's the safest/simplest I can think // Not the prettiest solution, but that's the safest/simplest I can think

View File

@ -23,41 +23,222 @@
// For content pages // For content pages
if ( typeof vAPI === 'object' ) { // >>>>>>>> start of HUGE-IF-BLOCK
/******************************************************************************/
/******************************************************************************/ /******************************************************************************/
(function() { vAPI.DOMFilterer = function() {
if ( typeof vAPI !== 'object' ) { return; } 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 = { this.userStylesheets = {
_userCSS: new Set(), current: new Set(),
_disabled: false, added: new Set(),
_send: function(add, css) { 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', { vAPI.messaging.send('vapi-background', {
what: 'userCSS', what: 'userCSS',
add: add, add: Array.from(this.added),
css: css remove: Array.from(this.removed)
}); });
}
this.added.clear();
this.removed.clear();
}, },
add: function(cssText) { add: function(cssText) {
if ( cssText === '' || this._userCSS.has(cssText) ) { return; } if ( cssText === '' ) { return; }
this._userCSS.add(cssText); this.added.add(cssText);
if ( this._disabled ) { return; }
this._send(true, cssText);
}, },
remove: function(cssText) { remove: function(cssText) {
if ( cssText === '' ) { return; } if ( cssText === '' ) { return; }
if ( this._userCSS.delete(cssText) && !this._disabled ) { this.removed.add(cssText);
this._send(true, cssText);
this._send(false, cssText);
}
}, },
toggle: function(state) { toggle: function(state) {
if ( state === undefined ) { state = this._disabled; } if ( state === undefined ) { state = this.disabled; }
if ( state !== this._disabled ) { return; } if ( state !== this.disabled ) { return; }
this._disabled = !state; this.disabled = !state;
if ( this._userCSS.size === 0 ) { return; } if ( this.current.size === 0 ) { return; }
this._send(state, Array.from(this._userCSS)); 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 // read-only
systemSettings: { systemSettings: {
compiledMagic: 'dhmexnfqwlom', compiledMagic: 'vrgorlgelgws',
selfieMagic: 'dhmexnfqwlom' selfieMagic: 'vrgorlgelgws'
}, },
restoreBackupSettings: { 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; api.writeOne = writeOneNoop;
logBuffer = null; logBuffer = null;
vAPI.messaging.broadcast({ what: 'loggerDisabled' });
} }
if ( logBuffer !== null ) { if ( logBuffer !== null ) {
vAPI.setTimeout(janitor, logBufferObsoleteAfter); vAPI.setTimeout(janitor, logBufferObsoleteAfter);

View File

@ -107,8 +107,6 @@ var onMessage = function(request, sender, callback) {
case 'cosmeticFiltersInjected': case 'cosmeticFiltersInjected':
µb.cosmeticFilteringEngine.addToSelectorCache(request); µb.cosmeticFilteringEngine.addToSelectorCache(request);
/* falls through */
case 'cosmeticFiltersActivated':
// Net-based cosmetic filters are of no interest for logging purpose. // Net-based cosmetic filters are of no interest for logging purpose.
if ( µb.logger.isEnabled() && request.type !== 'net' ) { if ( µb.logger.isEnabled() && request.type !== 'net' ) {
µb.logCosmeticFilters(tabId); µb.logCosmeticFilters(tabId);
@ -466,9 +464,12 @@ var onMessage = function(request, sender, callback) {
// Sync // Sync
var µb = µBlock, var µb = µBlock,
response, response,
tabId,
pageStore; pageStore;
if ( sender && sender.tab ) { if ( sender && sender.tab ) {
pageStore = µb.pageStoreFromTabId(sender.tab.id); tabId = sender.tab.id;
pageStore = µb.pageStoreFromTabId(tabId);
} }
switch ( request.what ) { switch ( request.what ) {
@ -476,7 +477,8 @@ var onMessage = function(request, sender, callback) {
response = { response = {
id: request.id, id: request.id,
hash: request.hash, hash: request.hash,
netSelectorCacheCountMax: µb.cosmeticFilteringEngine.netSelectorCacheCountMax netSelectorCacheCountMax:
µb.cosmeticFilteringEngine.netSelectorCacheCountMax
}; };
if ( if (
µb.userSettings.collapseBlocked && µb.userSettings.collapseBlocked &&
@ -490,22 +492,27 @@ var onMessage = function(request, sender, callback) {
case 'retrieveContentScriptParameters': case 'retrieveContentScriptParameters':
if ( pageStore && pageStore.getNetFilteringSwitch() ) { if ( pageStore && pageStore.getNetFilteringSwitch() ) {
response = { response = {
loggerEnabled: µb.logger.isEnabled(),
collapseBlocked: µb.userSettings.collapseBlocked, collapseBlocked: µb.userSettings.collapseBlocked,
noCosmeticFiltering: µb.cosmeticFilteringEngine.acceptedCount === 0 || pageStore.noCosmeticFiltering === true, noCosmeticFiltering:
noGenericCosmeticFiltering: pageStore.noGenericCosmeticFiltering === true µb.cosmeticFilteringEngine.acceptedCount === 0 ||
pageStore.noCosmeticFiltering === true,
noGenericCosmeticFiltering:
pageStore.noGenericCosmeticFiltering === true
}; };
response.specificCosmeticFilters = µb.cosmeticFilteringEngine.retrieveDomainSelectors( response.specificCosmeticFilters =
request, µb.cosmeticFilteringEngine
response.noCosmeticFiltering .retrieveDomainSelectors(request, sender, response);
); if ( request.isRootFrame && µb.logger.isEnabled() ) {
µb.logCosmeticFilters(tabId);
}
} }
break; break;
case 'retrieveGenericCosmeticSelectors': case 'retrieveGenericCosmeticSelectors':
if ( pageStore && pageStore.getGenericCosmeticFilteringSwitch() ) { if ( pageStore && pageStore.getGenericCosmeticFilteringSwitch() ) {
response = { response = {
result: µb.cosmeticFilteringEngine.retrieveGenericSelectors(request) result: µb.cosmeticFilteringEngine
.retrieveGenericSelectors(request)
}; };
} }
break; break;

View File

@ -338,11 +338,11 @@ RedirectEngine.prototype.toSelfie = function() {
} }
var µb = µBlock; var µb = µBlock;
return { return {
resources: µb.mapToArray(this.resources), resources: µb.arrayFrom(this.resources),
rules: rules, rules: rules,
ruleTypes: µb.setToArray(this.ruleTypes), ruleTypes: µb.arrayFrom(this.ruleTypes),
ruleSources: µb.setToArray(this.ruleSources), ruleSources: µb.arrayFrom(this.ruleSources),
ruleDestinations: µb.setToArray(this.ruleDestinations) ruleDestinations: µb.arrayFrom(this.ruleDestinations)
}; };
}; };
@ -359,11 +359,10 @@ RedirectEngine.prototype.fromSelfie = function(selfie) {
} }
// Rules. // Rules.
var µb = µBlock; this.rules = new Map(selfie.rules);
this.rules = µb.mapFromArray(selfie.rules); this.ruleTypes = new Set(selfie.ruleTypes);
this.ruleTypes = µb.setFromArray(selfie.ruleTypes); this.ruleSources = new Set(selfie.ruleSources);
this.ruleSources = µb.setFromArray(selfie.ruleSources); this.ruleDestinations = new Set(selfie.ruleDestinations);
this.ruleDestinations = µb.setFromArray(selfie.ruleDestinations);
return true; return true;
}; };

View File

@ -100,10 +100,27 @@ var fromCosmeticFilter = function(details) {
prefix = match[0], prefix = match[0],
filter = details.rawFilter.slice(prefix.length); 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 // https://github.com/gorhill/uBlock/issues/3101
// Use `m` flag for efficient regex execution. // Use `m` flag for efficient regex execution.
var reFilter = new RegExp( var reFilter = new RegExp(
'^\\[\\d,[^\\n]*\\\\*"' + '^\\[\\d,[^\\n]*\\\\*"' +
cssPrefixMatcher +
reEscapeCosmetic(filter) + reEscapeCosmetic(filter) +
'\\\\*"[^\\n]*\\]$', '\\\\*"[^\\n]*\\]$',
'gm' 'gm'
@ -154,8 +171,10 @@ var fromCosmeticFilter = function(details) {
while ( (match = reFilter.exec(content)) !== null ) { while ( (match = reFilter.exec(content)) !== null ) {
fargs = JSON.parse(match[0]); fargs = JSON.parse(match[0]);
switch ( fargs[0] ) { switch ( fargs[0] ) {
case 0: case 0: // id-based
case 2: case 2: // class-based
found = prefix + idOrClassPrefix + filter;
break;
case 4: case 4:
case 5: case 5:
case 7: case 7:
@ -169,6 +188,7 @@ var fromCosmeticFilter = function(details) {
break; break;
case 6: case 6:
case 8: case 8:
case 9:
if ( if (
fargs[2] === '' || fargs[2] === '' ||
reHostname.test(fargs[2]) === true || 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; return;
} }
var loggedSelectors = vAPI.loggedSelectors || {}, var reHasCSSCombinators = /[ >+~]/,
matchedSelectors = []; 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 DeclarativeSimpleJob = function(node) {
var evaluateSelector = function(selector) { 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 ( if (
loggedSelectors.hasOwnProperty(selector) === false && (this.node === document || this.node[matchProp](simple.str) === false) &&
document.querySelector(selector) !== null (this.node.querySelector(simple.str) === null)
) { ) {
loggedSelectors[selector] = true; return;
matchedSelectors.push(selector); }
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. var DeclarativeComplexJob = function() {
vAPI.domFilterer.simpleHideSelectors.entries.forEach(evaluateSelector); };
DeclarativeComplexJob.instance = null;
// Complex CSS selector-based cosmetic filters. DeclarativeComplexJob.create = function() {
vAPI.domFilterer.complexHideSelectors.entries.forEach(evaluateSelector); if ( DeclarativeComplexJob.instance === null ) {
DeclarativeComplexJob.instance = new DeclarativeComplexJob();
// Non-querySelector-able filters.
vAPI.domFilterer.nqsSelectors.forEach(function(filter) {
if ( loggedSelectors.hasOwnProperty(filter) === false ) {
loggedSelectors[filter] = true;
matchedSelectors.push(filter);
} }
}); return DeclarativeComplexJob.instance;
};
// Procedural cosmetic filters. DeclarativeComplexJob.prototype.lookup = function(out) {
vAPI.domFilterer.proceduralSelectors.entries.forEach(function(pfilter) { if ( complex.dict.size === 0 ) { return; }
if ( if ( complex.str === undefined ) {
loggedSelectors.hasOwnProperty(pfilter.raw) === false && complex.str = Array.from(complex.dict).join(',\n');
pfilter.exec().length !== 0
) {
loggedSelectors[pfilter.raw] = true;
matchedSelectors.push(pfilter.raw);
} }
}); 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; }
}
}
};
vAPI.loggedSelectors = loggedSelectors; 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; }
}
}
};
if ( matchedSelectors.length ) { 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( vAPI.messaging.send(
'scriptlets', 'scriptlets',
{ {
what: 'logCosmeticFilteringData', what: 'logCosmeticFilteringData',
frameURL: window.location.href, frameURL: window.location.href,
frameHostname: window.location.hostname, frameHostname: window.location.hostname,
matchedSelectors: matchedSelectors 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. 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 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 it under the terms of the GNU General Public License as published by
@ -23,20 +23,6 @@
/******************************************************************************/ /******************************************************************************/
(function() { if ( typeof vAPI === 'object' && vAPI.domFilterer ) {
if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { vAPI.domFilterer.toggle(false);
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();
})();

View File

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uBlock Origin - a browser extension to block requests. 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 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 it under the terms of the GNU General Public License as published by
@ -23,20 +23,6 @@
/******************************************************************************/ /******************************************************************************/
(function() { if ( typeof vAPI === 'object' && vAPI.domFilterer ) {
if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { vAPI.domFilterer.toggle(true);
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();
})();

View File

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uBlock Origin - a browser extension to block requests. 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 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 it under the terms of the GNU General Public License as published by
@ -24,24 +24,14 @@
/******************************************************************************/ /******************************************************************************/
(function() { (function() {
if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; }
return;
}
var xpr = document.evaluate(
'count(//*[@' + vAPI.domFilterer.hiddenId + '])',
document,
null,
XPathResult.NUMBER_TYPE,
null
);
vAPI.messaging.send( vAPI.messaging.send(
'scriptlets', 'scriptlets',
{ {
what: 'cosmeticallyFilteredElementCount', what: 'cosmeticallyFilteredElementCount',
pageURL: window.location.href, 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. 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 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 it under the terms of the GNU General Public License as published by
@ -140,11 +140,16 @@ var cssEscape = (function(/*root*/) {
// Highlighter-related // Highlighter-related
var svgRoot = null; var svgRoot = null;
var pickerRoot = null; var pickerRoot = null;
var highlightedElementLists = [ [], [], [] ];
var nodeToIdMap = new WeakMap(); // No need to iterate 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.lvl = 0;
this.sel = 'body'; this.sel = 'body';
this.cnt = 0; this.cnt = 0;
this.filter = nodeToCosmeticFilterMap.get(document.body); this.filter = roRedNodes.get(document.body);
}; };
var DomNode = function(node, level) { var DomNode = function(node, level) {
@ -232,7 +237,7 @@ var domLayout = (function() {
this.lvl = level; this.lvl = level;
this.sel = selectorFromNode(node); this.sel = selectorFromNode(node);
this.cnt = 0; this.cnt = 0;
this.filter = nodeToCosmeticFilterMap.get(node); this.filter = roRedNodes.get(node);
}; };
var domNodeFactory = function(level, node) { var domNodeFactory = function(level, node) {
@ -687,20 +692,27 @@ var cosmeticFilterMapper = (function() {
} }
var nodesFromStyleTag = function(rootNode) { var nodesFromStyleTag = function(rootNode) {
var filterMap = nodeToCosmeticFilterMap, var filterMap = roRedNodes,
selectors, selector, selectors, selector,
nodes, node, nodes, node,
i, j; i, j;
// CSS-based selectors: simple one. // Declarative selectors.
selectors = vAPI.domFilterer.simpleHideSelectors.entries; selectors = vAPI.domFilterer.getAllDeclarativeSelectors().split(',\n');
i = selectors.length; i = selectors.length;
while ( i-- ) { while ( i-- ) {
selector = selectors[i]; selector = selectors[i];
if ( filterMap.has(rootNode) === false && rootNode[matchesFnName](selector) ) { if ( reHasCSSCombinators.test(selector) ) {
nodes = document.querySelectorAll(selector);
} else {
if (
filterMap.has(rootNode) === false &&
rootNode[matchesFnName](selector)
) {
filterMap.set(rootNode, selector); filterMap.set(rootNode, selector);
} }
nodes = rootNode.querySelectorAll(selector); nodes = rootNode.querySelectorAll(selector);
}
j = nodes.length; j = nodes.length;
while ( j-- ) { while ( j-- ) {
node = nodes[j]; node = nodes[j];
@ -710,42 +722,31 @@ var cosmeticFilterMapper = (function() {
} }
} }
// CSS-based selectors: complex one (must query from doc root). // Procedural selectors.
selectors = vAPI.domFilterer.complexHideSelectors.entries; selectors = vAPI.domFilterer.getAllProceduralSelectors();
i = selectors.length; for ( var entry of selectors ) {
while ( i-- ) { nodes = entry[1].exec();
selector = selectors[i];
nodes = document.querySelectorAll(selector);
j = nodes.length; j = nodes.length;
while ( j-- ) { while ( j-- ) {
node = nodes[j];
if ( filterMap.has(node) === false ) { 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) { var incremental = function(rootNode) {
vAPI.domFilterer.userCSS.toggle(false); vAPI.domFilterer.toggle(false);
nodesFromStyleTag(rootNode); nodesFromStyleTag(rootNode);
}; };
var reset = function() { var reset = function() {
nodeToCosmeticFilterMap = new WeakMap(); roRedNodes = new Map();
incremental(document.documentElement); incremental(document.documentElement);
}; };
var shutdown = function() { var shutdown = function() {
vAPI.domFilterer.userCSS.toggle(true); vAPI.domFilterer.toggle(true);
}; };
return { return {
@ -824,25 +825,17 @@ var getSvgRootChildren = function() {
var highlightElements = function(scrollTo) { var highlightElements = function(scrollTo) {
var wv = pickerRoot.contentWindow.innerWidth; var wv = pickerRoot.contentWindow.innerWidth;
var hv = pickerRoot.contentWindow.innerHeight; var hv = pickerRoot.contentWindow.innerHeight;
var ocean = ['M0 0h' + wv + 'v' + hv + 'h-' + wv, 'z'], islands; var islands;
var elems, elem, rect, poly; var elem, rect, poly;
var xl, xr, yt, yb, w, h, ws; var xl, xr, yt, yb, w, h, ws;
var xlu = Number.MAX_VALUE, xru = 0, ytu = Number.MAX_VALUE, ybu = 0; var xlu = Number.MAX_VALUE, xru = 0, ytu = Number.MAX_VALUE, ybu = 0;
var lists = highlightedElementLists;
var svgRootChildren = getSvgRootChildren(); var svgRootChildren = getSvgRootChildren();
for ( var i = 0; i < lists.length; i++ ) {
elems = lists[i];
islands = []; islands = [];
for ( var j = 0; j < elems.length; j++ ) { for ( elem of rwRedNodes.keys() ) {
elem = elems[j]; if ( elem === pickerRoot ) { continue; }
if ( elem === pickerRoot ) { if ( rwGreenNodes.has(elem) ) { continue; }
continue; if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
}
if ( typeof elem.getBoundingClientRect !== 'function' ) {
continue;
}
rect = elem.getBoundingClientRect(); rect = elem.getBoundingClientRect();
xl = rect.left; xl = rect.left;
xr = rect.right; xr = rect.right;
@ -850,29 +843,78 @@ var highlightElements = function(scrollTo) {
yt = rect.top; yt = rect.top;
yb = rect.bottom; yb = rect.bottom;
h = rect.height; h = rect.height;
ws = w.toFixed(1); ws = w.toFixed(1);
poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) + poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
'h' + ws + 'h' + ws +
'v' + h.toFixed(1) + 'v' + h.toFixed(1) +
'h-' + ws + 'h-' + ws +
'z'; 'z';
ocean.push(poly);
islands.push(poly); islands.push(poly);
if ( !scrollTo ) {
continue;
} }
svgRootChildren[0].setAttribute('d', islands.join('') || 'M0 0');
if ( xl < xlu ) { xlu = xl; } islands = [];
if ( xr > xru ) { xru = xr; } for ( elem of rwGreenNodes ) {
if ( yt < ytu ) { ytu = yt; } if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
if ( yb > ybu ) { ybu = yb; } rect = elem.getBoundingClientRect();
} xl = rect.left;
svgRootChildren[i+1].setAttribute('d', islands.join('') || 'M0 0'); 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');
svgRoot.firstElementChild.setAttribute('d', ocean.join('')); 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 ) { if ( !scrollTo ) {
return; 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 selectNodes = function(selector, nid) {
var nodes = elementsFromSelector(selector); var nodes = elementsFromSelector(selector);
if ( nid === '' ) { if ( nid === '' ) { return nodes; }
return nodes; for ( var node of nodes ) {
} if ( nodeToIdMap.get(node) === nid ) {
var i = nodes.length; return [ node ];
while ( i-- ) {
if ( nodeToIdMap.get(nodes[i]) === nid ) {
return [nodes[i]];
} }
} }
return []; return [];
@ -950,84 +970,37 @@ var selectNodes = function(selector, nid) {
var shutdown = function() { var shutdown = function() {
cosmeticFilterMapper.shutdown(); cosmeticFilterMapper.shutdown();
resetToggledNodes();
domLayout.shutdown(); domLayout.shutdown();
vAPI.messaging.removeAllChannelListeners('domInspector'); vAPI.messaging.removeAllChannelListeners('domInspector');
window.removeEventListener('scroll', onScrolled, true); window.removeEventListener('scroll', onScrolled, true);
document.documentElement.removeChild(pickerRoot); document.documentElement.removeChild(pickerRoot);
pickerRoot = svgRoot = null; pickerRoot = svgRoot = null;
highlightedElementLists = [ [], [], [] ];
}; };
/******************************************************************************/ /******************************************************************************/
// original, target = what to do var toggleExceptions = function(nodes, targetState) {
// any, any = restore saved display property for ( var node of nodes ) {
// any, hidden = set display to `none`, remember original state if ( targetState ) {
// hidden, any = remove display property, don't remember original state rwGreenNodes.add(node);
// hidden, hidden = set display to `none` } else {
rwGreenNodes.delete(node);
}
}
};
var toggleNodes = function(nodes, originalState, targetState) { var toggleFilter = function(nodes, targetState) {
var i = nodes.length; for ( var node of nodes ) {
if ( i === 0 ) {
return;
}
var node, details;
while ( i-- ) {
node = nodes[i];
// originally visible node
if ( originalState ) {
// unhide visible node
if ( targetState ) { if ( targetState ) {
details = toggledNodes.get(node) || {}; rwRedNodes.delete(node);
showNode(node, details.v1, details.v2); } else {
toggledNodes.delete(node); rwRedNodes.add(node);
}
// hide visible node
else {
toggledNodes.set(node, {
show: true,
v1: node.style.getPropertyValue('display') || '',
v2: node.style.getPropertyPriority('display') || ''
});
hideNode(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);
}
} }
} }
}; };
// https://www.youtube.com/watch?v=L5jRewnxSBY // 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 ) { switch ( request.what ) {
case 'commitFilters': case 'commitFilters':
resetToggledNodes();
toggleNodes(selectNodes(request.hide, ''), true, false);
toggleNodes(selectNodes(request.unhide, ''), false, true);
forgetToggledNodes();
highlightedElementLists = [ [], [], [] ];
highlightElements(); highlightElements();
break; break;
@ -1053,44 +1021,38 @@ var onMessage = function(request) {
break; break;
case 'highlightMode': case 'highlightMode':
svgRoot.classList.toggle('invert', request.invert); //svgRoot.classList.toggle('invert', request.invert);
break; break;
case 'highlightOne': case 'highlightOne':
highlightedElementLists[0] = selectNodes(request.selector, request.nid); blueNodes = selectNodes(request.selector, request.nid);
highlightElements(request.scrollTo); highlightElements(request.scrollTo);
break; break;
case 'resetToggledNodes':
resetToggledNodes();
break;
case 'showCommitted': case 'showCommitted':
resetToggledNodes(); blueNodes = [];
highlightedElementLists[0] = []; // TODO: show only the new filters and exceptions.
highlightedElementLists[1] = selectNodes(request.hide, '');
highlightedElementLists[2] = selectNodes(request.unhide, '');
toggleNodes(highlightedElementLists[2], false, true);
highlightElements(true); highlightElements(true);
break; break;
case 'showInteractive': case 'showInteractive':
resetToggledNodes(); blueNodes = [];
toggleNodes(selectNodes(request.hide, ''), true, false);
toggleNodes(selectNodes(request.unhide, ''), false, true);
highlightedElementLists = [ [], [], [] ];
highlightElements(); highlightElements();
break; break;
case 'toggleFilter': case 'toggleFilter':
highlightedElementLists[0] = selectNodes(request.filter, request.nid); toggleExceptions(
toggleNodes(highlightedElementLists[0], request.original, request.target); selectNodes(request.filter, request.nid),
request.target
);
highlightElements(true); highlightElements(true);
break; break;
case 'toggleNodes': case 'toggleNodes':
highlightedElementLists[0] = selectNodes(request.selector, request.nid); toggleFilter(
toggleNodes(highlightedElementLists[0], request.original, request.target); selectNodes(request.selector, request.nid),
request.target
);
highlightElements(true); highlightElements(true);
break; break;
@ -1149,22 +1111,22 @@ pickerRoot.onload = function() {
'top: 0;', 'top: 0;',
'width: 100%;', 'width: 100%;',
'}', '}',
'svg > path:first-child {', 'svg > path:nth-of-type(1) {',
'fill: rgba(0,0,0,0.75);', 'fill: rgba(255,0,0,0.2);',
'fill-rule: evenodd;', 'stroke: #F00;',
'}', '}',
'svg > path:nth-of-type(2) {', 'svg > path:nth-of-type(2) {',
'fill: rgba(0,0,255,0.1);', 'fill: rgba(0,255,0,0.2);',
'stroke: #FFF;', 'stroke: #0F0;',
'stroke-width: 0.5px;',
'}', '}',
'svg > path:nth-of-type(3) {', 'svg > path:nth-of-type(3) {',
'fill: rgba(255,0,0,0.2);', 'fill: rgba(255,0,0,0.2);',
'stroke: #F00;', 'stroke: #F00;',
'}', '}',
'svg > path:nth-of-type(4) {', 'svg > path:nth-of-type(4) {',
'fill: rgba(0,255,0,0.2);', 'fill: rgba(0,0,255,0.1);',
'stroke: #0F0;', 'stroke: #FFF;',
'stroke-width: 0.5px;',
'}', '}',
'' ''
].join('\n'); ].join('\n');
@ -1179,8 +1141,8 @@ pickerRoot.onload = function() {
window.addEventListener('scroll', onScrolled, true); window.addEventListener('scroll', onScrolled, true);
highlightElements();
cosmeticFilterMapper.reset(); cosmeticFilterMapper.reset();
highlightElements();
vAPI.messaging.addChannelListener('domInspector', onMessage); vAPI.messaging.addChannelListener('domInspector', onMessage);
}; };

View File

@ -118,12 +118,11 @@
/******************************************************************************/ /******************************************************************************/
if ( typeof vAPI !== 'object' ) { if (
return; window.top !== window ||
} typeof vAPI !== 'object' ||
vAPI.domFilterer instanceof Object === false
// don't run in frames ) {
if ( window.top !== window ) {
return; return;
} }
@ -131,8 +130,8 @@ var pickerRoot = document.getElementById(vAPI.sessionId);
if ( pickerRoot ) { if ( pickerRoot ) {
return; return;
} }
var pickerBody = null; var pickerBody = null;
var pickerStyle = null;
var svgOcean = null; var svgOcean = null;
var svgIslands = null; var svgIslands = null;
var svgRoot = null; var svgRoot = null;
@ -1397,13 +1396,12 @@ var stopPicker = function() {
candidateElements = []; candidateElements = [];
bestCandidateFilter = null; bestCandidateFilter = null;
if ( pickerRoot === null ) { if ( pickerRoot === null ) { return; }
return;
}
// https://github.com/gorhill/uBlock/issues/2060 // https://github.com/gorhill/uBlock/issues/2060
if ( vAPI.userCSS ) { if ( vAPI.domFilterer instanceof Object ) {
vAPI.userCSS.remove(pickerStyle.textContent); vAPI.domFilterer.removeCSSRule(pickerCSSSelector1, pickerCSSDeclaration1);
vAPI.domFilterer.removeCSSRule(pickerCSSSelector2, pickerCSSDeclaration2);
} }
window.removeEventListener('scroll', onScrolled, true); window.removeEventListener('scroll', onScrolled, true);
@ -1414,7 +1412,6 @@ var stopPicker = function() {
svgRoot.removeEventListener('click', onSvgClicked); svgRoot.removeEventListener('click', onSvgClicked);
svgRoot.removeEventListener('touchstart', onSvgTouchStartStop); svgRoot.removeEventListener('touchstart', onSvgTouchStartStop);
svgRoot.removeEventListener('touchend', onSvgTouchStartStop); svgRoot.removeEventListener('touchend', onSvgTouchStartStop);
pickerStyle.parentNode.removeChild(pickerStyle);
pickerRoot.parentNode.removeChild(pickerRoot); pickerRoot.parentNode.removeChild(pickerRoot);
pickerRoot.removeEventListener('load', stopPicker); pickerRoot.removeEventListener('load', stopPicker);
pickerRoot = pickerRoot =
@ -1548,7 +1545,9 @@ var bootstrapPicker = function() {
pickerRoot = document.createElement('iframe'); pickerRoot = document.createElement('iframe');
pickerRoot.id = vAPI.sessionId; pickerRoot.id = vAPI.sessionId;
pickerRoot.style.cssText = [
var pickerCSSSelector1 = '#' + pickerRoot.id;
var pickerCSSDeclaration1 = [
'background: transparent', 'background: transparent',
'border: 0', 'border: 0',
'border-radius: 0', 'border-radius: 0',
@ -1569,29 +1568,28 @@ pickerRoot.style.cssText = [
'z-index: 2147483647', 'z-index: 2147483647',
'' ''
].join(' !important;'); ].join(' !important;');
var pickerCSSSelector2 = '[' + pickerRoot.id + '-clickblind]';
var pickerCSSDeclaration2 = 'pointer-events: none !important;';
pickerRoot.style.cssText = pickerCSSDeclaration1;
// https://github.com/gorhill/uBlock/issues/1529 // https://github.com/gorhill/uBlock/issues/1529
// In addition to inline styles, harden the element picker styles by using // In addition to inline styles, harden the element picker styles by using
// a dedicated style tag. // dedicated CSS rules.
pickerStyle = document.createElement('style'); vAPI.domFilterer.addCSSRule(
pickerStyle.textContent = [ pickerCSSSelector1,
'#' + pickerRoot.id + ' {', pickerCSSDeclaration1,
pickerRoot.style.cssText, { internal: true }
'}', );
'[' + pickerRoot.id + '-clickblind] {', vAPI.domFilterer.addCSSRule(
'pointer-events: none !important;', pickerCSSSelector2,
'}', pickerCSSDeclaration2,
'' { internal: true }
].join('\n'); );
document.documentElement.appendChild(pickerStyle);
// https://github.com/gorhill/uBlock/issues/2060 // https://github.com/gorhill/uBlock/issues/2060
if ( vAPI.domFilterer ) { vAPI.domFilterer.excludeNode(pickerRoot);
pickerRoot[vAPI.domFilterer.getExcludeId()] = true;
}
if ( vAPI.userCSS ) {
vAPI.userCSS.add(pickerStyle.textContent);
}
pickerRoot.addEventListener('load', bootstrapPicker); pickerRoot.addEventListener('load', bootstrapPicker);
document.documentElement.appendChild(pickerRoot); document.documentElement.appendChild(pickerRoot);

View File

@ -1137,12 +1137,12 @@ FilterHostnameDict.prototype.logData = function() {
}; };
FilterHostnameDict.prototype.compile = function() { FilterHostnameDict.prototype.compile = function() {
return [ this.fid, µb.setToArray(this.dict) ]; return [ this.fid, µb.arrayFrom(this.dict) ];
}; };
FilterHostnameDict.load = function(args) { FilterHostnameDict.load = function(args) {
var f = new FilterHostnameDict(); var f = new FilterHostnameDict();
f.dict = µb.setFromArray(args[1]); f.dict = new Set(args[1]);
return f; return f;
}; };
@ -2006,8 +2006,8 @@ FilterContainer.prototype.freeze = function() {
this.fdataLast = null; this.fdataLast = null;
this.filterLast = null; this.filterLast = null;
this.frozen = true; this.frozen = true;
//console.log(JSON.stringify(Array.from(filterClassHistogram))); //console.log(JSON.stringify(µb.arrayFrom(filterClassHistogram)));
//this.tokenHistogram = new Map(Array.from(this.tokenHistogram).sort(function(a, b) { //this.tokenHistogram = new Map(µb.arrayFrom(this.tokenHistogram).sort(function(a, b) {
// return a[0].localeCompare(b[0]) || (b[1] - a[1]); // return a[0].localeCompare(b[0]) || (b[1] - a[1]);
//})); //}));
}; };

View File

@ -211,7 +211,7 @@
this.removeFilterList(oldKeys[i]); this.removeFilterList(oldKeys[i]);
} }
} }
newKeys = this.setToArray(newSet); newKeys = this.arrayFrom(newSet);
var bin = { var bin = {
selectedFilterLists: newKeys, selectedFilterLists: newKeys,
remoteBlacklists: this.oldDataFromNewListKeys(newKeys) remoteBlacklists: this.oldDataFromNewListKeys(newKeys)
@ -342,10 +342,10 @@
} }
selectedListKeySet.add(assetKey); 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 ) { if ( externalLists !== this.userSettings.externalLists ) {
this.userSettings.externalLists = externalLists; this.userSettings.externalLists = externalLists;
vAPI.storage.set({ externalLists: externalLists }); vAPI.storage.set({ externalLists: externalLists });
@ -371,7 +371,7 @@
} }
out.add(location); 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 ? Array.from
: function(map) { : function(iterable) {
var out = []; var out = [], i = 0;
for ( var entry of map ) { for ( var value of iterable ) {
out.push(entry); out[i++] = value;
} }
return out; 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) { µ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/css/* $DES/css/
cp platform/firefox/polyfill.js $DES/js/ cp platform/firefox/polyfill.js $DES/js/
cp platform/firefox/vapi-*.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/bootstrap.js $DES/
cp platform/firefox/processScript.js $DES/ cp platform/firefox/processScript.js $DES/
cp platform/firefox/frame*.js $DES/ cp platform/firefox/frame*.js $DES/