2014-06-24 00:42:43 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2016-03-06 16:51:06 +01:00
|
|
|
uBlock Origin - a browser extension to block requests.
|
2017-02-12 21:53:40 +01:00
|
|
|
Copyright (C) 2014-2017 Raymond Hill
|
2014-06-24 00:42:43 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
'use strict';
|
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
+--> domCollapser
|
|
|
|
|
|
|
|
|
|
|
|
|
|
domWatcher--+
|
|
|
|
| +-- domSurveyor
|
|
|
|
| |
|
|
|
|
+--> domFilterer --+-- domLogger
|
|
|
|
|
|
|
|
|
+-- domInspector
|
2016-08-12 14:55:35 +02:00
|
|
|
|
|
|
|
domWatcher:
|
|
|
|
Watches for changes in the DOM, and notify the other components about these
|
|
|
|
changes.
|
|
|
|
|
|
|
|
domCollapser:
|
|
|
|
Enforces the collapsing of DOM elements for which a corresponding
|
|
|
|
resource was blocked through network filtering.
|
|
|
|
|
|
|
|
domFilterer:
|
|
|
|
Enforces the filtering of DOM elements, by feeding it cosmetic filters.
|
|
|
|
|
|
|
|
domSurveyor:
|
|
|
|
Surveys the DOM to find new cosmetic filters to apply to the current page.
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
domLogger:
|
|
|
|
Surveys the page to find and report the injected cosmetic filters blocking
|
|
|
|
actual elements on the current page. This component is dynamically loaded
|
|
|
|
IF AND ONLY IF uBO's logger is opened.
|
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
If page is whitelisted:
|
|
|
|
- domWatcher: off
|
|
|
|
- domCollapser: off
|
|
|
|
- domFilterer: off
|
|
|
|
- domSurveyor: off
|
2017-10-21 19:43:46 +02:00
|
|
|
- domLogger: off
|
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
I verified that the code in this file is completely flushed out of memory
|
|
|
|
when a page is whitelisted.
|
|
|
|
|
|
|
|
If cosmetic filtering is disabled:
|
|
|
|
- domWatcher: on
|
|
|
|
- domCollapser: on
|
|
|
|
- domFilterer: off
|
|
|
|
- domSurveyor: off
|
2017-10-21 19:43:46 +02:00
|
|
|
- domLogger: off
|
2016-08-12 14:55:35 +02:00
|
|
|
|
|
|
|
If generic cosmetic filtering is disabled:
|
|
|
|
- domWatcher: on
|
|
|
|
- domCollapser: on
|
|
|
|
- domFilterer: on
|
|
|
|
- domSurveyor: off
|
2017-10-21 19:43:46 +02:00
|
|
|
- domLogger: on if uBO logger is opened
|
|
|
|
|
|
|
|
If generic cosmetic filtering is enabled:
|
|
|
|
- domWatcher: on
|
|
|
|
- domCollapser: on
|
|
|
|
- domFilterer: on
|
|
|
|
- domSurveyor: on
|
|
|
|
- domLogger: on if uBO logger is opened
|
2016-08-12 14:55:35 +02:00
|
|
|
|
|
|
|
Additionally, the domSurveyor can turn itself off once it decides that
|
|
|
|
it has become pointless (repeatedly not finding new cosmetic filters).
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
The domFilterer makes use of platform-dependent user stylesheets[1].
|
|
|
|
|
2016-12-16 22:25:36 +01:00
|
|
|
At time of writing, only modern Firefox provides a custom implementation,
|
|
|
|
which makes for solid, reliable and low overhead cosmetic filtering on
|
|
|
|
Firefox.
|
2017-10-21 19:43:46 +02:00
|
|
|
|
2016-12-16 22:25:36 +01:00
|
|
|
The generic implementation[2] performs as best as can be, but won't ever be
|
2017-10-21 19:43:46 +02:00
|
|
|
as reliable and accurate as real user stylesheets.
|
|
|
|
|
|
|
|
[1] "user stylesheets" refer to local CSS rules which have priority over,
|
|
|
|
and can't be overriden by a web page's own CSS rules.
|
2016-12-16 22:25:36 +01:00
|
|
|
[2] below, see platformUserCSS / platformHideNode / platformUnhideNode
|
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
*/
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2017-08-17 14:25:02 +02:00
|
|
|
// Abort execution if our global vAPI object does not exist.
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/456
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2029
|
|
|
|
|
2017-10-27 20:22:45 +02:00
|
|
|
if ( typeof vAPI === 'object' && !vAPI.contentScript ) { // >>>>>>>> start of HUGE-IF-BLOCK
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
vAPI.contentScript = true;
|
2017-08-17 14:25:02 +02:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
2017-10-24 22:38:51 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
The purpose of SafeAnimationFrame is to take advantage of the behavior of
|
|
|
|
window.requestAnimationFrame[1]. If we use an animation frame as a timer,
|
|
|
|
then this timer is described as follow:
|
|
|
|
|
|
|
|
- time events are throttled by the browser when the viewport is not visible --
|
|
|
|
there is no point for uBO to play with the DOM if the document is not
|
|
|
|
visible.
|
|
|
|
- time events are micro tasks[2].
|
|
|
|
- time events are synchronized to monitor refresh, meaning that they can fire
|
|
|
|
at most 1/60 (typically).
|
|
|
|
|
|
|
|
If a delay value is provided, a plain timer is first used. Plain timers are
|
|
|
|
macro-tasks, so this is good when uBO wants to yield to more important tasks
|
|
|
|
on a page. Once the plain timer elapse, an animation frame is used to trigger
|
|
|
|
the next time at which to execute the job.
|
|
|
|
|
|
|
|
[1] https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
|
|
|
|
[2] https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
|
|
|
|
|
|
|
|
*/
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2016-11-12 19:38:41 +01:00
|
|
|
// https://github.com/gorhill/uBlock/issues/2147
|
|
|
|
|
|
|
|
vAPI.SafeAnimationFrame = function(callback) {
|
|
|
|
this.fid = this.tid = null;
|
|
|
|
this.callback = callback;
|
2017-10-24 22:38:51 +02:00
|
|
|
this.boundMacroToMicro = this.macroToMicro.bind(this);
|
2016-11-12 19:38:41 +01:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.SafeAnimationFrame.prototype = {
|
|
|
|
start: function(delay) {
|
|
|
|
if ( delay === undefined ) {
|
|
|
|
if ( this.fid === null ) {
|
|
|
|
this.fid = requestAnimationFrame(this.callback);
|
|
|
|
}
|
|
|
|
if ( this.tid === null ) {
|
2017-10-31 11:47:39 +01:00
|
|
|
this.tid = vAPI.setTimeout(this.callback, 20000);
|
2017-10-21 19:43:46 +02:00
|
|
|
}
|
2017-10-24 22:38:51 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( this.fid === null && this.tid === null ) {
|
|
|
|
this.tid = vAPI.setTimeout(this.boundMacroToMicro, delay);
|
2017-10-21 19:43:46 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
clear: function() {
|
|
|
|
if ( this.fid !== null ) { cancelAnimationFrame(this.fid); }
|
|
|
|
if ( this.tid !== null ) { clearTimeout(this.tid); }
|
|
|
|
this.fid = this.tid = null;
|
2017-10-24 22:38:51 +02:00
|
|
|
},
|
|
|
|
macroToMicro: function() {
|
|
|
|
this.tid = null;
|
|
|
|
this.start();
|
2017-08-21 18:04:35 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.domWatcher = (function() {
|
2017-07-23 15:56:43 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var addedNodeLists = [],
|
|
|
|
addedNodes = [],
|
|
|
|
domIsReady = false,
|
|
|
|
domLayoutObserver,
|
|
|
|
ignoreTags = new Set([ 'br', 'head', 'link', 'meta', 'script', 'style' ]),
|
|
|
|
listeners = [],
|
|
|
|
listenerIterator = [], listenerIteratorDirty = false,
|
|
|
|
removedNodeLists = [],
|
|
|
|
removedNodes = false,
|
|
|
|
safeObserverHandlerTimer;
|
2016-12-16 22:25:36 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var safeObserverHandler = function() {
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.time('dom watcher/safe observer handler');
|
2017-10-21 19:43:46 +02:00
|
|
|
safeObserverHandlerTimer.clear();
|
|
|
|
var i = addedNodeLists.length,
|
|
|
|
j = addedNodes.length,
|
|
|
|
nodeList, iNode, node;
|
|
|
|
while ( i-- ) {
|
|
|
|
nodeList = addedNodeLists[i];
|
|
|
|
iNode = nodeList.length;
|
|
|
|
while ( iNode-- ) {
|
|
|
|
node = nodeList[iNode];
|
|
|
|
if ( node.nodeType !== 1 ) { continue; }
|
|
|
|
if ( ignoreTags.has(node.localName) ) { continue; }
|
|
|
|
if ( node.parentElement === null ) { continue; }
|
|
|
|
addedNodes[j++] = node;
|
2016-12-16 22:25:36 +01:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
}
|
|
|
|
addedNodeLists.length = 0;
|
|
|
|
i = removedNodeLists.length;
|
|
|
|
while ( i-- && removedNodes === false ) {
|
|
|
|
nodeList = removedNodeLists[i];
|
|
|
|
iNode = nodeList.length;
|
|
|
|
while ( iNode-- ) {
|
|
|
|
if ( nodeList[iNode].nodeType !== 1 ) { continue; }
|
|
|
|
removedNodes = true;
|
|
|
|
break;
|
2016-12-16 22:25:36 +01:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
}
|
|
|
|
removedNodeLists.length = 0;
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.timeEnd('dom watcher/safe observer handler');
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( addedNodes.length === 0 && removedNodes === false ) { return; }
|
|
|
|
for ( var listener of getListenerIterator() ) {
|
|
|
|
listener.onDOMChanged(addedNodes, removedNodes);
|
|
|
|
}
|
|
|
|
addedNodes.length = 0;
|
|
|
|
removedNodes = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/205
|
|
|
|
// Do not handle added node directly from within mutation observer.
|
|
|
|
var observerHandler = function(mutations) {
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.time('dom watcher/observer handler');
|
2017-10-21 19:43:46 +02:00
|
|
|
var nodeList, mutation,
|
|
|
|
i = mutations.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
mutation = mutations[i];
|
|
|
|
nodeList = mutation.addedNodes;
|
|
|
|
if ( nodeList.length !== 0 ) {
|
|
|
|
addedNodeLists.push(nodeList);
|
2016-12-16 22:25:36 +01:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( removedNodes ) { continue; }
|
|
|
|
nodeList = mutation.removedNodes;
|
|
|
|
if ( nodeList.length !== 0 ) {
|
|
|
|
removedNodeLists.push(nodeList);
|
2016-12-16 22:25:36 +01:00
|
|
|
}
|
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( addedNodeLists.length !== 0 || removedNodes ) {
|
2017-10-25 17:42:18 +02:00
|
|
|
safeObserverHandlerTimer.start(1);
|
2017-10-21 19:43:46 +02:00
|
|
|
}
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.timeEnd('dom watcher/observer handler');
|
2016-12-16 22:25:36 +01:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var startMutationObserver = function() {
|
|
|
|
if ( domLayoutObserver !== undefined || !domIsReady ) { return; }
|
|
|
|
domLayoutObserver = new MutationObserver(observerHandler);
|
|
|
|
domLayoutObserver.observe(document.documentElement, {
|
|
|
|
//attributeFilter: [ 'class', 'id' ],
|
|
|
|
//attributes: true,
|
|
|
|
childList: true,
|
|
|
|
subtree: true
|
|
|
|
});
|
|
|
|
safeObserverHandlerTimer = new vAPI.SafeAnimationFrame(safeObserverHandler);
|
|
|
|
vAPI.shutdown.add(cleanup);
|
|
|
|
};
|
2016-12-15 16:47:32 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var stopMutationObserver = function() {
|
|
|
|
if ( domLayoutObserver === undefined ) { return; }
|
|
|
|
cleanup();
|
|
|
|
vAPI.shutdown.remove(cleanup);
|
|
|
|
};
|
2016-12-15 16:47:32 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var getListenerIterator = function() {
|
|
|
|
if ( listenerIteratorDirty ) {
|
|
|
|
listenerIterator = listeners.slice();
|
|
|
|
listenerIteratorDirty = false;
|
2017-03-12 15:22:46 +01:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return listenerIterator;
|
2016-12-15 16:47:32 +01:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var addListener = function(listener) {
|
|
|
|
if ( listeners.indexOf(listener) !== -1 ) { return; }
|
|
|
|
listeners.push(listener);
|
|
|
|
listenerIteratorDirty = true;
|
2017-10-25 17:42:18 +02:00
|
|
|
if ( domIsReady !== true ) { return; }
|
|
|
|
listener.onDOMCreated();
|
|
|
|
startMutationObserver();
|
2016-12-15 16:47:32 +01:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var removeListener = function(listener) {
|
|
|
|
var pos = listeners.indexOf(listener);
|
|
|
|
if ( pos === -1 ) { return; }
|
|
|
|
listeners.splice(pos, 1);
|
|
|
|
listenerIteratorDirty = true;
|
|
|
|
if ( listeners.length === 0 ) {
|
|
|
|
stopMutationObserver();
|
2017-03-12 15:22:46 +01:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
var cleanup = function() {
|
|
|
|
if ( domLayoutObserver !== undefined ) {
|
|
|
|
domLayoutObserver.disconnect();
|
|
|
|
domLayoutObserver = null;
|
2016-12-15 16:47:32 +01:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( safeObserverHandlerTimer !== undefined ) {
|
|
|
|
safeObserverHandlerTimer.clear();
|
|
|
|
safeObserverHandlerTimer = undefined;
|
2016-12-15 16:47:32 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var start = function() {
|
|
|
|
domIsReady = true;
|
|
|
|
for ( var listener of getListenerIterator() ) {
|
|
|
|
listener.onDOMCreated();
|
2016-12-15 16:47:32 +01:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
startMutationObserver();
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
start: start,
|
|
|
|
addListener: addListener,
|
|
|
|
removeListener: removeListener
|
2016-12-15 16:47:32 +01:00
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
2016-12-15 16:47:32 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.matchesProp = (function() {
|
|
|
|
var docElem = document.documentElement;
|
|
|
|
if ( typeof docElem.matches !== 'function' ) {
|
|
|
|
if ( typeof docElem.mozMatchesSelector === 'function' ) {
|
|
|
|
return 'mozMatchesSelector';
|
|
|
|
} else if ( typeof docElem.webkitMatchesSelector === 'function' ) {
|
|
|
|
return 'webkitMatchesSelector';
|
|
|
|
} else if ( typeof docElem.msMatchesSelector === 'function' ) {
|
|
|
|
return 'msMatchesSelector';
|
2016-12-25 22:56:39 +01:00
|
|
|
}
|
2016-08-06 18:09:18 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return 'matches';
|
|
|
|
})();
|
2016-08-06 18:09:18 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
2016-08-06 18:09:18 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.injectScriptlet = function(doc, text) {
|
|
|
|
if ( !doc ) { return; }
|
|
|
|
try {
|
|
|
|
var script = doc.createElement('script');
|
|
|
|
script.appendChild(doc.createTextNode(text));
|
|
|
|
(doc.head || doc.documentElement).appendChild(script);
|
|
|
|
} catch (ex) {
|
2016-08-06 18:09:18 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/*******************************************************************************
|
2016-08-06 18:09:18 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
The DOM filterer is the heart of uBO's cosmetic filtering.
|
2016-12-25 22:56:39 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
DOMBaseFilterer: platform-specific
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+---- DOMFilterer: adds procedural cosmetic filtering
|
2016-12-25 22:56:39 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
*/
|
2016-12-25 22:56:39 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.DOMFilterer = (function() {
|
2016-08-06 18:09:18 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
// 'P' stands for 'Procedural'
|
2016-08-06 18:09:18 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var PSelectorHasTask = function(task) {
|
|
|
|
this.selector = task[1];
|
|
|
|
};
|
|
|
|
PSelectorHasTask.prototype.exec = function(input) {
|
|
|
|
var output = [];
|
2017-10-28 22:50:44 +02:00
|
|
|
for ( var node of input ) {
|
|
|
|
if ( node.querySelector(this.selector) !== null ) {
|
|
|
|
output.push(node);
|
2016-12-30 16:32:17 +01:00
|
|
|
}
|
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return output;
|
|
|
|
};
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var PSelectorHasTextTask = function(task) {
|
|
|
|
this.needle = new RegExp(task[1]);
|
|
|
|
};
|
|
|
|
PSelectorHasTextTask.prototype.exec = function(input) {
|
|
|
|
var output = [];
|
2017-10-28 22:50:44 +02:00
|
|
|
for ( var node of input ) {
|
|
|
|
if ( this.needle.test(node.textContent) ) {
|
|
|
|
output.push(node);
|
2016-07-10 01:21:46 +02:00
|
|
|
}
|
2016-06-28 01:09:04 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return output;
|
|
|
|
};
|
2016-06-29 01:45:11 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var PSelectorIfTask = function(task) {
|
|
|
|
this.pselector = new PSelector(task[1]);
|
|
|
|
};
|
|
|
|
PSelectorIfTask.prototype.target = true;
|
|
|
|
PSelectorIfTask.prototype.exec = function(input) {
|
|
|
|
var output = [];
|
2017-10-28 22:50:44 +02:00
|
|
|
for ( var node of input ) {
|
|
|
|
if ( this.pselector.test(node) === this.target ) {
|
|
|
|
output.push(node);
|
2016-07-10 01:21:46 +02:00
|
|
|
}
|
2016-06-29 01:45:11 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return output;
|
|
|
|
};
|
2016-06-29 01:45:11 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var PSelectorIfNotTask = function(task) {
|
|
|
|
PSelectorIfTask.call(this, task);
|
|
|
|
this.target = false;
|
|
|
|
};
|
|
|
|
PSelectorIfNotTask.prototype = Object.create(PSelectorIfTask.prototype);
|
|
|
|
PSelectorIfNotTask.prototype.constructor = PSelectorIfNotTask;
|
2016-06-29 01:45:11 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var PSelectorMatchesCSSTask = function(task) {
|
|
|
|
this.name = task[1].name;
|
|
|
|
this.value = new RegExp(task[1].value);
|
|
|
|
};
|
|
|
|
PSelectorMatchesCSSTask.prototype.pseudo = null;
|
|
|
|
PSelectorMatchesCSSTask.prototype.exec = function(input) {
|
|
|
|
var output = [], style;
|
2017-10-28 22:50:44 +02:00
|
|
|
for ( var node of input ) {
|
|
|
|
style = window.getComputedStyle(node, this.pseudo);
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( style === null ) { return null; } /* FF */
|
|
|
|
if ( this.value.test(style[this.name]) ) {
|
2017-10-28 22:50:44 +02:00
|
|
|
output.push(node);
|
2016-09-22 18:18:01 +02:00
|
|
|
}
|
2016-07-12 19:29:30 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return output;
|
|
|
|
};
|
2016-07-12 19:29:30 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var PSelectorMatchesCSSAfterTask = function(task) {
|
|
|
|
PSelectorMatchesCSSTask.call(this, task);
|
|
|
|
this.pseudo = ':after';
|
|
|
|
};
|
|
|
|
PSelectorMatchesCSSAfterTask.prototype = Object.create(PSelectorMatchesCSSTask.prototype);
|
|
|
|
PSelectorMatchesCSSAfterTask.prototype.constructor = PSelectorMatchesCSSAfterTask;
|
2016-09-24 20:42:31 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var PSelectorMatchesCSSBeforeTask = function(task) {
|
|
|
|
PSelectorMatchesCSSTask.call(this, task);
|
|
|
|
this.pseudo = ':before';
|
|
|
|
};
|
|
|
|
PSelectorMatchesCSSBeforeTask.prototype = Object.create(PSelectorMatchesCSSTask.prototype);
|
|
|
|
PSelectorMatchesCSSBeforeTask.prototype.constructor = PSelectorMatchesCSSBeforeTask;
|
2016-06-29 01:45:11 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var PSelectorXpathTask = function(task) {
|
|
|
|
this.xpe = document.createExpression(task[1], null);
|
|
|
|
this.xpr = null;
|
|
|
|
};
|
|
|
|
PSelectorXpathTask.prototype.exec = function(input) {
|
2017-10-28 22:50:44 +02:00
|
|
|
var output = [], j;
|
|
|
|
for ( var node of input ) {
|
2017-10-21 19:43:46 +02:00
|
|
|
this.xpr = this.xpe.evaluate(
|
2017-10-28 22:50:44 +02:00
|
|
|
node,
|
2017-10-21 19:43:46 +02:00
|
|
|
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
|
|
|
|
this.xpr
|
2016-06-29 01:45:11 +02:00
|
|
|
);
|
2017-10-21 19:43:46 +02:00
|
|
|
j = this.xpr.snapshotLength;
|
|
|
|
while ( j-- ) {
|
|
|
|
node = this.xpr.snapshotItem(j);
|
|
|
|
if ( node.nodeType === 1 ) {
|
|
|
|
output.push(node);
|
|
|
|
}
|
|
|
|
}
|
2016-06-29 01:45:11 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return output;
|
|
|
|
};
|
2016-06-29 01:45:11 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var PSelector = function(o) {
|
|
|
|
if ( PSelector.prototype.operatorToTaskMap === undefined ) {
|
|
|
|
PSelector.prototype.operatorToTaskMap = new Map([
|
|
|
|
[ ':has', PSelectorHasTask ],
|
|
|
|
[ ':has-text', PSelectorHasTextTask ],
|
|
|
|
[ ':if', PSelectorIfTask ],
|
|
|
|
[ ':if-not', PSelectorIfNotTask ],
|
|
|
|
[ ':matches-css', PSelectorMatchesCSSTask ],
|
|
|
|
[ ':matches-css-after', PSelectorMatchesCSSAfterTask ],
|
|
|
|
[ ':matches-css-before', PSelectorMatchesCSSBeforeTask ],
|
|
|
|
[ ':xpath', PSelectorXpathTask ]
|
|
|
|
]);
|
|
|
|
}
|
2017-10-24 22:38:51 +02:00
|
|
|
this.budget = 250; // I arbitrary picked a 1/4 second
|
2017-10-21 19:43:46 +02:00
|
|
|
this.raw = o.raw;
|
2017-10-23 15:01:00 +02:00
|
|
|
this.cost = 0;
|
2017-10-21 19:43:46 +02:00
|
|
|
this.selector = o.selector;
|
|
|
|
this.tasks = [];
|
|
|
|
var tasks = o.tasks;
|
|
|
|
if ( !tasks ) { return; }
|
2017-10-28 22:50:44 +02:00
|
|
|
for ( var task of tasks ) {
|
|
|
|
this.tasks.push(new (this.operatorToTaskMap.get(task[0]))(task));
|
2016-07-12 19:29:30 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
};
|
|
|
|
PSelector.prototype.operatorToTaskMap = undefined;
|
|
|
|
PSelector.prototype.prime = function(input) {
|
|
|
|
var root = input || document;
|
|
|
|
if ( this.selector !== '' ) {
|
|
|
|
return root.querySelectorAll(this.selector);
|
2016-08-14 14:51:52 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return [ root ];
|
|
|
|
};
|
|
|
|
PSelector.prototype.exec = function(input) {
|
2017-10-28 22:50:44 +02:00
|
|
|
var nodes = this.prime(input);
|
|
|
|
for ( var task of this.tasks ) {
|
|
|
|
if ( nodes.length === 0 ) { break; }
|
|
|
|
nodes = task.exec(nodes);
|
2016-07-10 01:21:46 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return nodes;
|
|
|
|
};
|
|
|
|
PSelector.prototype.test = function(input) {
|
2017-10-28 22:50:44 +02:00
|
|
|
var nodes = this.prime(input), AA = [ null ], aa;
|
|
|
|
for ( var node of nodes ) {
|
|
|
|
AA[0] = node; aa = AA;
|
|
|
|
for ( var task of this.tasks ) {
|
|
|
|
aa = task.exec(aa);
|
|
|
|
if ( aa.length === 0 ) { break; }
|
2017-10-21 19:43:46 +02:00
|
|
|
}
|
|
|
|
if ( aa.length !== 0 ) { return true; }
|
2016-07-10 01:21:46 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return false;
|
|
|
|
};
|
2016-11-12 19:38:41 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var DOMProceduralFilterer = function(domFilterer) {
|
|
|
|
this.domFilterer = domFilterer;
|
|
|
|
this.domIsReady = false;
|
|
|
|
this.domIsWatched = false;
|
|
|
|
this.addedSelectors = new Map();
|
|
|
|
this.addedNodes = false;
|
|
|
|
this.removedNodes = false;
|
|
|
|
this.addedNodesHandlerMissCount = 0;
|
|
|
|
this.currentResultset = new Set();
|
|
|
|
this.selectors = new Map();
|
|
|
|
};
|
2014-10-19 13:11:27 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
DOMProceduralFilterer.prototype = {
|
|
|
|
|
|
|
|
addProceduralSelectors: function(aa) {
|
|
|
|
var raw, o, pselector,
|
|
|
|
mustCommit = this.domIsWatched;
|
|
|
|
for ( var i = 0, n = aa.length; i < n; i++ ) {
|
|
|
|
raw = aa[i];
|
|
|
|
o = JSON.parse(raw);
|
|
|
|
if ( o.style ) {
|
|
|
|
this.domFilterer.addCSSRule(o.style[0], o.style[1]);
|
|
|
|
mustCommit = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( o.pseudoclass ) {
|
|
|
|
this.domFilterer.addCSSRule(
|
|
|
|
o.raw,
|
2017-10-23 15:01:00 +02:00
|
|
|
'display:none!important;'
|
2017-10-21 19:43:46 +02:00
|
|
|
);
|
|
|
|
mustCommit = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( o.tasks ) {
|
|
|
|
if ( this.selectors.has(raw) === false ) {
|
|
|
|
pselector = new PSelector(o);
|
|
|
|
this.selectors.set(raw, pselector);
|
|
|
|
this.addedSelectors.set(raw, pselector);
|
|
|
|
mustCommit = true;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( mustCommit === false ) { return; }
|
|
|
|
this.domFilterer.commit();
|
|
|
|
this.domFilterer.triggerListeners(
|
|
|
|
'procedural',
|
|
|
|
new Map(this.addedSelectors)
|
|
|
|
);
|
|
|
|
},
|
2015-01-13 21:52:15 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
commitNow: function() {
|
|
|
|
if ( this.selectors.size === 0 || this.domIsReady === false ) {
|
|
|
|
return;
|
|
|
|
}
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( this.addedNodes || this.removedNodes ) {
|
|
|
|
this.addedSelectors.clear();
|
|
|
|
}
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var currentResultset = this.currentResultset,
|
|
|
|
entry, nodes, i, node;
|
|
|
|
|
|
|
|
if ( this.addedSelectors.size !== 0 ) {
|
2017-10-31 11:47:39 +01:00
|
|
|
//console.time('procedural selectors/filterset changed');
|
2017-10-21 19:43:46 +02:00
|
|
|
for ( entry of this.addedSelectors ) {
|
|
|
|
nodes = entry[1].exec();
|
|
|
|
i = nodes.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
node = nodes[i];
|
|
|
|
this.domFilterer.hideNode(node);
|
|
|
|
currentResultset.add(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.addedSelectors.clear();
|
2017-10-31 11:47:39 +01:00
|
|
|
//console.timeEnd('procedural selectors/filterset changed');
|
2017-10-21 19:43:46 +02:00
|
|
|
return;
|
|
|
|
}
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2017-10-31 11:47:39 +01:00
|
|
|
//console.time('procedural selectors/dom layout changed');
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
this.addedNodes = this.removedNodes = false;
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2017-10-23 15:01:00 +02:00
|
|
|
var afterResultset = new Set(),
|
2017-10-24 22:38:51 +02:00
|
|
|
t0 = Date.now(), t1, cost, pselector;
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
for ( entry of this.selectors ) {
|
2017-10-23 15:01:00 +02:00
|
|
|
pselector = entry[1];
|
2017-10-24 22:38:51 +02:00
|
|
|
if ( pselector.budget <= 0 ) { continue; }
|
2017-10-23 15:01:00 +02:00
|
|
|
nodes = pselector.exec();
|
2017-10-21 19:43:46 +02:00
|
|
|
i = nodes.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
node = nodes[i];
|
|
|
|
this.domFilterer.hideNode(node);
|
|
|
|
afterResultset.add(node);
|
|
|
|
}
|
2017-10-23 15:01:00 +02:00
|
|
|
t1 = Date.now();
|
2017-10-24 22:38:51 +02:00
|
|
|
cost = t1 - t0;
|
2017-10-23 15:01:00 +02:00
|
|
|
t0 = t1;
|
2017-10-24 22:38:51 +02:00
|
|
|
if ( cost <= 8 ) { continue; }
|
|
|
|
pselector.budget -= cost;
|
|
|
|
if ( pselector.budget <= 0 ) {
|
|
|
|
console.log('disabling %s', pselector.raw);
|
|
|
|
}
|
2016-07-10 01:21:46 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( afterResultset.size !== currentResultset.size ) {
|
|
|
|
this.addedNodesHandlerMissCount = 0;
|
|
|
|
} else {
|
|
|
|
this.addedNodesHandlerMissCount += 1;
|
2016-07-10 01:21:46 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
for ( node of currentResultset ) {
|
|
|
|
if ( afterResultset.has(node) === false ) {
|
|
|
|
this.domFilterer.unhideNode(node);
|
|
|
|
}
|
2016-07-10 01:21:46 +02:00
|
|
|
}
|
2015-01-02 02:58:19 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
this.currentResultset = afterResultset;
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2017-10-31 11:47:39 +01:00
|
|
|
//console.timeEnd('procedural selectors/dom layout changed');
|
2016-07-10 01:21:46 +02:00
|
|
|
},
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
createProceduralFilter: function(o) {
|
|
|
|
return new PSelector(o);
|
|
|
|
},
|
2016-06-28 01:09:04 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
onDOMCreated: function() {
|
|
|
|
this.domIsReady = true;
|
2017-10-23 18:21:37 +02:00
|
|
|
this.domFilterer.commitNow();
|
2017-10-21 19:43:46 +02:00
|
|
|
},
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
onDOMChanged: function(addedNodes, removedNodes) {
|
|
|
|
if ( this.selectors.size === 0 ) { return; }
|
|
|
|
this.addedNodes = this.addedNodes || addedNodes.length !== 0;
|
|
|
|
this.removedNodes = this.removedNodes || removedNodes;
|
|
|
|
this.domFilterer.commit();
|
|
|
|
}
|
|
|
|
};
|
2016-08-12 14:55:35 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var DOMFiltererBase = vAPI.DOMFilterer;
|
2016-08-12 14:55:35 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var domFilterer = function() {
|
|
|
|
DOMFiltererBase.call(this);
|
|
|
|
this.exceptions = [];
|
|
|
|
this.proceduralFilterer = new DOMProceduralFilterer(this);
|
2017-10-23 18:21:37 +02:00
|
|
|
this.hideNodeAttr = undefined;
|
|
|
|
this.hideNodeStyleSheetInjected = false;
|
2017-10-21 19:43:46 +02:00
|
|
|
|
|
|
|
// May or may not exist: cache locally since this may be called often.
|
|
|
|
this.baseOnDOMChanged = DOMFiltererBase.prototype.onDOMChanged;
|
|
|
|
|
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
|
|
|
vAPI.domWatcher.addListener(this);
|
2016-08-12 14:55:35 +02:00
|
|
|
}
|
|
|
|
};
|
2017-10-21 19:43:46 +02:00
|
|
|
domFilterer.prototype = Object.create(DOMFiltererBase.prototype);
|
|
|
|
domFilterer.prototype.constructor = domFilterer;
|
2016-08-12 14:55:35 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
domFilterer.prototype.commitNow = function() {
|
|
|
|
DOMFiltererBase.prototype.commitNow.call(this);
|
|
|
|
this.proceduralFilterer.commitNow();
|
|
|
|
};
|
2016-11-12 19:38:41 +01:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
domFilterer.prototype.addProceduralSelectors = function(aa) {
|
|
|
|
this.proceduralFilterer.addProceduralSelectors(aa);
|
2016-08-12 14:55:35 +02:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
domFilterer.prototype.createProceduralFilter = function(o) {
|
|
|
|
return this.proceduralFilterer.createProceduralFilter(o);
|
2016-08-12 14:55:35 +02:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
domFilterer.prototype.getAllProceduralSelectors = function() {
|
|
|
|
return new Map(this.proceduralFilterer.selectors);
|
2016-08-12 14:55:35 +02:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
domFilterer.prototype.getAllExceptionSelectors = function() {
|
|
|
|
return this.exceptions.join(',\n');
|
2016-08-12 14:55:35 +02:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
domFilterer.prototype.onDOMCreated = function() {
|
|
|
|
if ( DOMFiltererBase.prototype.onDOMCreated !== undefined ) {
|
|
|
|
DOMFiltererBase.prototype.onDOMCreated.call(this);
|
|
|
|
}
|
|
|
|
this.proceduralFilterer.onDOMCreated();
|
|
|
|
};
|
|
|
|
|
|
|
|
domFilterer.prototype.onDOMChanged = function() {
|
|
|
|
if ( this.baseOnDOMChanged !== undefined ) {
|
|
|
|
this.baseOnDOMChanged.apply(this, arguments);
|
|
|
|
}
|
|
|
|
this.proceduralFilterer.onDOMChanged.apply(
|
|
|
|
this.proceduralFilterer,
|
|
|
|
arguments
|
|
|
|
);
|
2016-08-12 14:55:35 +02:00
|
|
|
};
|
2017-10-21 19:43:46 +02:00
|
|
|
|
|
|
|
return domFilterer;
|
2016-08-12 14:55:35 +02:00
|
|
|
})();
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.domFilterer = new vAPI.DOMFilterer();
|
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
vAPI.domCollapser = (function() {
|
2017-08-03 16:18:05 +02:00
|
|
|
var resquestIdGenerator = 1,
|
|
|
|
processTimer,
|
|
|
|
toProcess = [],
|
|
|
|
toFilter = [],
|
|
|
|
toCollapse = new Map(),
|
|
|
|
cachedBlockedSet,
|
|
|
|
cachedBlockedSetHash,
|
|
|
|
cachedBlockedSetTimer;
|
2015-06-04 17:17:02 +02:00
|
|
|
var src1stProps = {
|
2015-03-29 18:13:28 +02:00
|
|
|
'embed': 'src',
|
2017-08-03 16:18:05 +02:00
|
|
|
'iframe': 'src',
|
2015-03-29 18:13:28 +02:00
|
|
|
'img': 'src',
|
|
|
|
'object': 'data'
|
|
|
|
};
|
2015-06-04 17:17:02 +02:00
|
|
|
var src2ndProps = {
|
|
|
|
'img': 'srcset'
|
|
|
|
};
|
2017-08-03 16:18:05 +02:00
|
|
|
var tagToTypeMap = {
|
|
|
|
embed: 'object',
|
|
|
|
iframe: 'sub_frame',
|
|
|
|
img: 'image',
|
|
|
|
object: 'object'
|
|
|
|
};
|
|
|
|
var netSelectorCacheCount = 0,
|
|
|
|
messaging = vAPI.messaging;
|
2017-08-16 20:10:41 +02:00
|
|
|
|
2017-08-03 16:18:05 +02:00
|
|
|
var cachedBlockedSetClear = function() {
|
|
|
|
cachedBlockedSet =
|
|
|
|
cachedBlockedSetHash =
|
|
|
|
cachedBlockedSetTimer = undefined;
|
2015-03-29 18:13:28 +02:00
|
|
|
};
|
|
|
|
|
2017-08-03 16:18:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/174
|
|
|
|
// Do not remove fragment from src URL
|
2015-04-08 01:10:03 +02:00
|
|
|
var onProcessed = function(response) {
|
2017-08-17 00:06:04 +02:00
|
|
|
if ( !response ) { // This happens if uBO is disabled or restarted.
|
2017-08-03 16:18:05 +02:00
|
|
|
toCollapse.clear();
|
2016-03-21 15:33:40 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-08-03 16:18:05 +02:00
|
|
|
|
|
|
|
var targets = toCollapse.get(response.id);
|
|
|
|
if ( targets === undefined ) { return; }
|
|
|
|
toCollapse.delete(response.id);
|
|
|
|
if ( cachedBlockedSetHash !== response.hash ) {
|
|
|
|
cachedBlockedSet = new Set(response.blockedResources);
|
|
|
|
cachedBlockedSetHash = response.hash;
|
|
|
|
if ( cachedBlockedSetTimer !== undefined ) {
|
|
|
|
clearTimeout(cachedBlockedSetTimer);
|
|
|
|
}
|
|
|
|
cachedBlockedSetTimer = vAPI.setTimeout(cachedBlockedSetClear, 30000);
|
2015-03-29 18:13:28 +02:00
|
|
|
}
|
2017-08-16 20:10:41 +02:00
|
|
|
if ( cachedBlockedSet === undefined || cachedBlockedSet.size === 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
2016-08-08 15:53:35 +02:00
|
|
|
var selectors = [],
|
2017-08-03 16:18:05 +02:00
|
|
|
iframeLoadEventPatch = vAPI.iframeLoadEventPatch,
|
2016-08-08 15:53:35 +02:00
|
|
|
netSelectorCacheCountMax = response.netSelectorCacheCountMax,
|
2017-08-03 16:18:05 +02:00
|
|
|
tag, prop, src, value;
|
|
|
|
|
|
|
|
for ( var target of targets ) {
|
|
|
|
tag = target.localName;
|
|
|
|
prop = src1stProps[tag];
|
|
|
|
if ( prop === undefined ) { continue; }
|
|
|
|
src = target[prop];
|
|
|
|
if ( typeof src !== 'string' || src.length === 0 ) {
|
|
|
|
prop = src2ndProps[tag];
|
|
|
|
if ( prop === undefined ) { continue; }
|
|
|
|
src = target[prop];
|
|
|
|
if ( typeof src !== 'string' || src.length === 0 ) { continue; }
|
2015-03-29 18:13:28 +02:00
|
|
|
}
|
2017-08-03 16:18:05 +02:00
|
|
|
if ( cachedBlockedSet.has(tagToTypeMap[tag] + ' ' + src) === false ) {
|
2015-03-29 18:13:28 +02:00
|
|
|
continue;
|
|
|
|
}
|
2017-08-03 16:18:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/399
|
|
|
|
// Never remove elements from the DOM, just hide them
|
|
|
|
target.style.setProperty('display', 'none', 'important');
|
|
|
|
target.hidden = true;
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/1048
|
|
|
|
// Use attribute to construct CSS rule
|
|
|
|
if (
|
|
|
|
netSelectorCacheCount <= netSelectorCacheCountMax &&
|
|
|
|
(value = target.getAttribute(prop))
|
|
|
|
) {
|
|
|
|
selectors.push(tag + '[' + prop + '="' + value + '"]');
|
|
|
|
netSelectorCacheCount += 1;
|
2016-08-08 15:53:35 +02:00
|
|
|
}
|
2017-08-03 16:18:05 +02:00
|
|
|
if ( iframeLoadEventPatch !== undefined ) {
|
|
|
|
iframeLoadEventPatch(target);
|
2015-03-29 18:13:28 +02:00
|
|
|
}
|
|
|
|
}
|
2017-10-22 14:59:29 +02:00
|
|
|
|
2015-03-29 18:13:28 +02:00
|
|
|
if ( selectors.length !== 0 ) {
|
2016-03-06 16:51:06 +01:00
|
|
|
messaging.send(
|
|
|
|
'contentscript',
|
|
|
|
{
|
|
|
|
what: 'cosmeticFiltersInjected',
|
|
|
|
type: 'net',
|
|
|
|
hostname: window.location.hostname,
|
|
|
|
selectors: selectors
|
|
|
|
}
|
|
|
|
);
|
2015-03-29 18:13:28 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var send = function() {
|
2017-08-03 16:18:05 +02:00
|
|
|
processTimer = undefined;
|
|
|
|
toCollapse.set(resquestIdGenerator, toProcess);
|
|
|
|
var msg = {
|
|
|
|
what: 'getCollapsibleBlockedRequests',
|
|
|
|
id: resquestIdGenerator,
|
2017-10-01 13:56:28 +02:00
|
|
|
frameURL: window.location.href,
|
2017-08-03 16:18:05 +02:00
|
|
|
resources: toFilter,
|
|
|
|
hash: cachedBlockedSetHash
|
|
|
|
};
|
|
|
|
messaging.send('contentscript', msg, onProcessed);
|
|
|
|
toProcess = [];
|
|
|
|
toFilter = [];
|
|
|
|
resquestIdGenerator += 1;
|
2015-03-29 18:13:28 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
var process = function(delay) {
|
2017-08-03 16:18:05 +02:00
|
|
|
if ( toProcess.length === 0 ) { return; }
|
2015-03-29 18:13:28 +02:00
|
|
|
if ( delay === 0 ) {
|
2017-08-03 16:18:05 +02:00
|
|
|
if ( processTimer !== undefined ) {
|
|
|
|
clearTimeout(processTimer);
|
|
|
|
}
|
2015-03-29 18:13:28 +02:00
|
|
|
send();
|
2017-08-03 16:18:05 +02:00
|
|
|
} else if ( processTimer === undefined ) {
|
|
|
|
processTimer = vAPI.setTimeout(send, delay || 20);
|
2015-03-29 18:13:28 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var add = function(target) {
|
2017-08-03 16:18:05 +02:00
|
|
|
toProcess[toProcess.length] = target;
|
2015-03-29 18:13:28 +02:00
|
|
|
};
|
|
|
|
|
2016-02-04 01:15:28 +01:00
|
|
|
var addMany = function(targets) {
|
|
|
|
var i = targets.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
add(targets[i]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-05-02 01:06:52 +02:00
|
|
|
var iframeSourceModified = function(mutations) {
|
|
|
|
var i = mutations.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
addIFrame(mutations[i].target, true);
|
|
|
|
}
|
|
|
|
process();
|
|
|
|
};
|
|
|
|
var iframeSourceObserver = new MutationObserver(iframeSourceModified);
|
|
|
|
var iframeSourceObserverOptions = {
|
|
|
|
attributes: true,
|
|
|
|
attributeFilter: [ 'src' ]
|
|
|
|
};
|
|
|
|
|
2016-01-21 15:33:54 +01:00
|
|
|
var primeLocalIFrame = function(iframe) {
|
|
|
|
// Should probably also copy injected styles.
|
2016-03-05 20:59:01 +01:00
|
|
|
// The injected scripts are those which were injected in the current
|
|
|
|
// document, from within the `contentscript-start.js / injectScripts`,
|
|
|
|
// and which scripts are selectively looked-up from:
|
|
|
|
// https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
|
2016-01-21 15:33:54 +01:00
|
|
|
if ( vAPI.injectedScripts ) {
|
2017-08-21 18:04:35 +02:00
|
|
|
vAPI.injectScriptlet(iframe.contentDocument, vAPI.injectedScripts);
|
2016-01-21 15:33:54 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-05-02 01:06:52 +02:00
|
|
|
var addIFrame = function(iframe, dontObserve) {
|
|
|
|
// https://github.com/gorhill/uBlock/issues/162
|
|
|
|
// Be prepared to deal with possible change of src attribute.
|
|
|
|
if ( dontObserve !== true ) {
|
|
|
|
iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
|
|
|
|
}
|
|
|
|
|
2015-03-29 18:13:28 +02:00
|
|
|
var src = iframe.src;
|
|
|
|
if ( src === '' || typeof src !== 'string' ) {
|
2016-01-21 15:33:54 +01:00
|
|
|
primeLocalIFrame(iframe);
|
2015-03-29 18:13:28 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-08-03 16:18:05 +02:00
|
|
|
if ( src.lastIndexOf('http', 0) !== 0 ) { return; }
|
|
|
|
toFilter[toFilter.length] = {
|
|
|
|
type: 'sub_frame',
|
|
|
|
url: iframe.src
|
|
|
|
};
|
|
|
|
add(iframe);
|
2015-03-29 18:13:28 +02:00
|
|
|
};
|
|
|
|
|
2016-02-04 01:15:28 +01:00
|
|
|
var addIFrames = function(iframes) {
|
2016-02-04 00:47:30 +01:00
|
|
|
var i = iframes.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
addIFrame(iframes[i]);
|
|
|
|
}
|
2016-02-04 01:15:28 +01:00
|
|
|
};
|
|
|
|
|
2016-08-13 22:42:58 +02:00
|
|
|
var onResourceFailed = function(ev) {
|
2017-08-03 16:18:05 +02:00
|
|
|
if ( tagToTypeMap[ev.target.localName] !== undefined ) {
|
2017-10-21 19:43:46 +02:00
|
|
|
add(ev.target);
|
|
|
|
process();
|
2017-08-03 16:18:05 +02:00
|
|
|
}
|
2016-08-13 22:42:58 +02:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var domWatcherInterface = {
|
|
|
|
onDOMCreated: function() {
|
|
|
|
if ( vAPI instanceof Object === false ) { return; }
|
|
|
|
if ( vAPI.domCollapser instanceof Object === false ) {
|
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
|
|
|
vAPI.domWatcher.removeListener(domWatcherInterface);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Listener to collapse blocked resources.
|
|
|
|
// - Future requests not blocked yet
|
|
|
|
// - Elements dynamically added to the page
|
|
|
|
// - Elements which resource URL changes
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/7
|
|
|
|
// Preferring getElementsByTagName over querySelectorAll:
|
|
|
|
// http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
|
|
|
|
var elems = document.images || document.getElementsByTagName('img'),
|
|
|
|
i = elems.length, elem;
|
|
|
|
while ( i-- ) {
|
|
|
|
elem = elems[i];
|
|
|
|
if ( elem.complete ) {
|
|
|
|
add(elem);
|
|
|
|
}
|
2016-08-12 14:55:35 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
addMany(document.embeds || document.getElementsByTagName('embed'));
|
|
|
|
addMany(document.getElementsByTagName('object'));
|
|
|
|
addIFrames(document.getElementsByTagName('iframe'));
|
|
|
|
process(0);
|
|
|
|
|
|
|
|
document.addEventListener('error', onResourceFailed, true);
|
|
|
|
|
|
|
|
vAPI.shutdown.add(function() {
|
|
|
|
document.removeEventListener('error', onResourceFailed, true);
|
|
|
|
if ( processTimer !== undefined ) {
|
|
|
|
clearTimeout(processTimer);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
onDOMChanged: function(addedNodes) {
|
|
|
|
var ni = addedNodes.length;
|
|
|
|
if ( ni === 0 ) { return; }
|
|
|
|
for ( var i = 0, node; i < ni; i++ ) {
|
|
|
|
node = addedNodes[i];
|
|
|
|
if ( node.localName === 'iframe' ) {
|
|
|
|
addIFrame(node);
|
|
|
|
}
|
|
|
|
if ( node.childElementCount === 0 ) { continue; }
|
2016-08-12 14:55:35 +02:00
|
|
|
var iframes = node.getElementsByTagName('iframe');
|
|
|
|
if ( iframes.length !== 0 ) {
|
|
|
|
addIFrames(iframes);
|
|
|
|
}
|
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
process();
|
2016-06-29 01:45:11 +02:00
|
|
|
}
|
2016-08-12 14:55:35 +02:00
|
|
|
};
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
|
|
|
vAPI.domWatcher.addListener(domWatcherInterface);
|
|
|
|
}
|
2015-03-29 18:13:28 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
add: add,
|
2016-02-04 01:15:28 +01:00
|
|
|
addMany: addMany,
|
2015-03-29 18:13:28 +02:00
|
|
|
addIFrame: addIFrame,
|
2016-02-04 01:15:28 +01:00
|
|
|
addIFrames: addIFrames,
|
2017-10-21 19:43:46 +02:00
|
|
|
process: process
|
2015-03-29 18:13:28 +02:00
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
2016-06-28 01:09:04 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
vAPI.domSurveyor = (function() {
|
2017-10-21 19:43:46 +02:00
|
|
|
var messaging = vAPI.messaging,
|
|
|
|
domFilterer,
|
2017-10-25 17:42:18 +02:00
|
|
|
hostname = '',
|
2017-10-21 19:43:46 +02:00
|
|
|
queriedIds = new Set(),
|
|
|
|
queriedClasses = new Set(),
|
|
|
|
pendingIdNodes = { nodes: [], added: [] },
|
|
|
|
pendingClassNodes = { nodes: [], added: [] },
|
2016-10-06 16:49:46 +02:00
|
|
|
surveyCost = 0;
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
// This is to shutdown the surveyor if result of surveying keeps being
|
2017-10-24 22:38:51 +02:00
|
|
|
// fruitless. This is useful on long-lived web page. I arbitrarily
|
|
|
|
// picked 5 minutes before the surveyor is allowed to shutdown. I also
|
|
|
|
// arbitrarily picked 256 misses before the surveyor is allowed to
|
|
|
|
// shutdown.
|
|
|
|
var canShutdownAfter = Date.now() + 300000,
|
|
|
|
surveyingMissCount = 0;
|
2017-10-21 19:43:46 +02:00
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
// Handle main process' response.
|
|
|
|
|
|
|
|
var surveyPhase3 = function(response) {
|
|
|
|
var result = response && response.result,
|
2017-10-21 19:43:46 +02:00
|
|
|
mustCommit = false;
|
2015-04-08 01:10:03 +02:00
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
if ( result ) {
|
2017-10-21 19:43:46 +02:00
|
|
|
var selectors = result.simple;
|
|
|
|
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
|
|
|
|
domFilterer.addCSSRule(
|
|
|
|
selectors,
|
2017-10-23 15:01:00 +02:00
|
|
|
'display:none!important;',
|
2017-10-21 19:43:46 +02:00
|
|
|
{ type: 'simple' }
|
|
|
|
);
|
|
|
|
mustCommit = true;
|
2014-08-12 18:19:54 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
selectors = result.complex;
|
|
|
|
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
|
|
|
|
domFilterer.addCSSRule(
|
|
|
|
selectors,
|
2017-10-23 15:01:00 +02:00
|
|
|
'display:none!important;',
|
2017-10-21 19:43:46 +02:00
|
|
|
{ type: 'complex' }
|
|
|
|
);
|
|
|
|
mustCommit = true;
|
2014-08-12 18:19:54 +02:00
|
|
|
}
|
2017-10-26 12:18:03 +02:00
|
|
|
selectors = result.injected;
|
|
|
|
if ( typeof selectors === 'string' && selectors.length !== 0 ) {
|
|
|
|
domFilterer.addCSSRule(
|
|
|
|
selectors,
|
|
|
|
'display:none!important;',
|
|
|
|
{ injected: true }
|
|
|
|
);
|
|
|
|
mustCommit = true;
|
|
|
|
}
|
2016-08-12 14:55:35 +02:00
|
|
|
}
|
2016-07-12 19:29:30 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( hasChunk(pendingIdNodes) || hasChunk(pendingClassNodes) ) {
|
|
|
|
surveyTimer.start(1);
|
2016-08-12 14:55:35 +02:00
|
|
|
}
|
2016-06-30 22:10:38 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( mustCommit ) {
|
|
|
|
surveyingMissCount = 0;
|
2017-10-24 22:38:51 +02:00
|
|
|
canShutdownAfter = Date.now() + 300000;
|
2017-10-21 19:43:46 +02:00
|
|
|
return;
|
2016-08-12 14:55:35 +02:00
|
|
|
}
|
2014-07-04 22:47:34 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
surveyingMissCount += 1;
|
2017-10-24 22:38:51 +02:00
|
|
|
if ( surveyingMissCount < 256 || Date.now() < canShutdownAfter ) {
|
|
|
|
return;
|
|
|
|
}
|
2014-07-04 22:47:34 +02:00
|
|
|
|
2017-10-24 22:38:51 +02:00
|
|
|
console.log('dom surveyor shutting down: too many misses');
|
2014-09-17 01:16:18 +02:00
|
|
|
|
2017-10-24 22:38:51 +02:00
|
|
|
surveyTimer.clear();
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.domWatcher.removeListener(domWatcherInterface);
|
|
|
|
vAPI.domSurveyor = null;
|
2016-08-12 14:55:35 +02:00
|
|
|
};
|
2014-07-04 22:47:34 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var surveyTimer = new vAPI.SafeAnimationFrame(function() {
|
|
|
|
surveyPhase1();
|
|
|
|
});
|
2014-09-17 01:16:18 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
// The purpose of "chunkification" is to ensure the surveyor won't unduly
|
|
|
|
// block the main event loop.
|
2014-09-17 01:16:18 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var hasChunk = function(pending) {
|
|
|
|
return pending.nodes.length !== 0 ||
|
|
|
|
pending.added.length !== 0;
|
2016-08-12 14:55:35 +02:00
|
|
|
};
|
2015-09-04 22:30:53 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var addChunk = function(pending, added) {
|
|
|
|
if ( added.length === 0 ) { return; }
|
2016-08-12 14:55:35 +02:00
|
|
|
if (
|
2017-10-21 19:43:46 +02:00
|
|
|
Array.isArray(added) === false ||
|
|
|
|
pending.added.length === 0 ||
|
|
|
|
Array.isArray(pending.added[0]) === false ||
|
|
|
|
pending.added[0].length >= 1000
|
2016-08-12 14:55:35 +02:00
|
|
|
) {
|
2017-10-21 19:43:46 +02:00
|
|
|
pending.added.push(added);
|
|
|
|
} else {
|
|
|
|
pending.added = pending.added.concat(added);
|
2016-08-12 14:55:35 +02:00
|
|
|
}
|
|
|
|
};
|
2014-07-30 14:05:00 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var nextChunk = function(pending) {
|
|
|
|
var added = pending.added.length !== 0 ? pending.added.shift() : [],
|
|
|
|
nodes;
|
|
|
|
if ( pending.nodes.length === 0 ) {
|
|
|
|
if ( added.length <= 1000 ) { return added; }
|
|
|
|
nodes = Array.isArray(added)
|
|
|
|
? added
|
|
|
|
: Array.prototype.slice.call(added);
|
|
|
|
pending.nodes = nodes.splice(1000);
|
|
|
|
return nodes;
|
|
|
|
}
|
|
|
|
if ( Array.isArray(added) === false ) {
|
|
|
|
added = Array.prototype.slice.call(added);
|
|
|
|
}
|
|
|
|
if ( pending.nodes.length < 1000 ) {
|
|
|
|
nodes = pending.nodes.concat(added.splice(0, 1000 - pending.nodes.length));
|
|
|
|
pending.nodes = added;
|
|
|
|
} else {
|
|
|
|
nodes = pending.nodes.splice(0, 1000);
|
|
|
|
pending.nodes = pending.nodes.concat(added);
|
2016-08-12 14:55:35 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
return nodes;
|
2014-08-12 18:19:54 +02:00
|
|
|
};
|
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
// Extract all classes/ids: these will be passed to the cosmetic
|
|
|
|
// filtering engine, and in return we will obtain only the relevant
|
|
|
|
// CSS selectors.
|
2014-09-16 21:39:21 +02:00
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
// https://github.com/gorhill/uBlock/issues/672
|
|
|
|
// http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens
|
|
|
|
// http://jsperf.com/enumerate-classes/6
|
2016-06-30 22:10:38 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var surveyPhase1 = function() {
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.time('dom surveyor/surveying');
|
2017-10-21 19:43:46 +02:00
|
|
|
surveyTimer.clear();
|
|
|
|
var t0 = window.performance.now();
|
|
|
|
var rews = reWhitespace,
|
|
|
|
qq, iout, nodes, i, node, v, vv, j;
|
|
|
|
var ids = [];
|
|
|
|
iout = 0;
|
|
|
|
qq = queriedIds;
|
|
|
|
nodes = nextChunk(pendingIdNodes);
|
2017-02-12 21:53:40 +01:00
|
|
|
i = nodes.length;
|
2016-08-12 14:55:35 +02:00
|
|
|
while ( i-- ) {
|
|
|
|
node = nodes[i];
|
|
|
|
v = node.id;
|
2017-02-12 21:53:40 +01:00
|
|
|
if ( typeof v !== 'string' ) { continue; }
|
2017-10-21 19:43:46 +02:00
|
|
|
v = v.trim();
|
|
|
|
if ( qq.has(v) === false && v.length !== 0 ) {
|
|
|
|
ids[iout++] = v; qq.add(v);
|
2014-07-30 14:05:00 +02:00
|
|
|
}
|
2017-02-12 21:53:40 +01:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
var classes = [];
|
|
|
|
iout = 0;
|
|
|
|
qq = queriedClasses;
|
|
|
|
nodes = nextChunk(pendingClassNodes);
|
2017-02-12 21:53:40 +01:00
|
|
|
i = nodes.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
node = nodes[i];
|
2016-08-12 14:55:35 +02:00
|
|
|
vv = node.className;
|
2017-02-12 21:53:40 +01:00
|
|
|
if ( typeof vv !== 'string' ) { continue; }
|
2017-08-16 20:10:41 +02:00
|
|
|
if ( rews.test(vv) === false ) {
|
2017-10-21 19:43:46 +02:00
|
|
|
if ( qq.has(vv) === false && vv.length !== 0 ) {
|
|
|
|
classes[iout++] = vv; qq.add(vv);
|
2017-02-12 21:53:40 +01:00
|
|
|
}
|
2016-07-10 01:21:46 +02:00
|
|
|
} else {
|
2016-08-12 14:55:35 +02:00
|
|
|
vv = node.classList;
|
|
|
|
j = vv.length;
|
|
|
|
while ( j-- ) {
|
2017-10-21 19:43:46 +02:00
|
|
|
v = vv[j];
|
2017-08-16 20:10:41 +02:00
|
|
|
if ( qq.has(v) === false ) {
|
2017-10-21 19:43:46 +02:00
|
|
|
classes[iout++] = v; qq.add(v);
|
2017-02-12 21:53:40 +01:00
|
|
|
}
|
2016-07-12 19:29:30 +02:00
|
|
|
}
|
2016-07-10 01:21:46 +02:00
|
|
|
}
|
2014-07-30 14:05:00 +02:00
|
|
|
}
|
2016-10-06 16:49:46 +02:00
|
|
|
surveyCost += window.performance.now() - t0;
|
2017-10-21 19:43:46 +02:00
|
|
|
// Phase 2: Ask main process to lookup relevant cosmetic filters.
|
|
|
|
if ( ids.length !== 0 || classes.length !== 0 ) {
|
|
|
|
messaging.send(
|
|
|
|
'contentscript',
|
|
|
|
{
|
|
|
|
what: 'retrieveGenericCosmeticSelectors',
|
2017-10-25 17:42:18 +02:00
|
|
|
hostname: hostname,
|
2017-10-21 19:43:46 +02:00
|
|
|
ids: ids.join('\n'),
|
|
|
|
classes: classes.join('\n'),
|
|
|
|
exceptions: domFilterer.exceptions,
|
|
|
|
cost: surveyCost
|
|
|
|
},
|
|
|
|
surveyPhase3
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
surveyPhase3(null);
|
|
|
|
}
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.timeEnd('dom surveyor/surveying');
|
2014-07-02 18:02:29 +02:00
|
|
|
};
|
2017-02-12 21:53:40 +01:00
|
|
|
var reWhitespace = /\s/;
|
2014-08-12 18:19:54 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
var domWatcherInterface = {
|
|
|
|
onDOMCreated: function() {
|
|
|
|
if (
|
|
|
|
vAPI instanceof Object === false ||
|
2017-10-24 22:38:51 +02:00
|
|
|
vAPI.domSurveyor instanceof Object === false ||
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.domFilterer instanceof Object === false
|
|
|
|
) {
|
|
|
|
if ( vAPI instanceof Object ) {
|
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
|
|
|
vAPI.domWatcher.removeListener(domWatcherInterface);
|
|
|
|
}
|
2017-10-24 22:38:51 +02:00
|
|
|
vAPI.domSurveyor = null;
|
2017-10-21 19:43:46 +02:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.time('dom surveyor/dom layout created');
|
2017-10-21 19:43:46 +02:00
|
|
|
domFilterer = vAPI.domFilterer;
|
|
|
|
addChunk(pendingIdNodes, document.querySelectorAll('[id]'));
|
|
|
|
addChunk(pendingClassNodes, document.querySelectorAll('[class]'));
|
|
|
|
surveyTimer.start();
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.timeEnd('dom surveyor/dom layout created');
|
2017-10-21 19:43:46 +02:00
|
|
|
},
|
|
|
|
onDOMChanged: function(addedNodes) {
|
|
|
|
if ( addedNodes.length === 0 ) { return; }
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.time('dom surveyor/dom layout changed');
|
2017-10-21 19:43:46 +02:00
|
|
|
var idNodes = [], iid = 0,
|
|
|
|
classNodes = [], iclass = 0;
|
|
|
|
var i = addedNodes.length,
|
|
|
|
node, nodeList, j;
|
|
|
|
while ( i-- ) {
|
|
|
|
node = addedNodes[i];
|
|
|
|
idNodes[iid++] = node;
|
|
|
|
classNodes[iclass++] = node;
|
|
|
|
if ( node.childElementCount === 0 ) { continue; }
|
|
|
|
nodeList = node.querySelectorAll('[id]');
|
|
|
|
j = nodeList.length;
|
|
|
|
while ( j-- ) {
|
|
|
|
idNodes[iid++] = nodeList[j];
|
|
|
|
}
|
|
|
|
nodeList = node.querySelectorAll('[class]');
|
|
|
|
j = nodeList.length;
|
|
|
|
while ( j-- ) {
|
|
|
|
classNodes[iclass++] = nodeList[j];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( idNodes.length !== 0 || classNodes.lengh !== 0 ) {
|
|
|
|
addChunk(pendingIdNodes, idNodes);
|
|
|
|
addChunk(pendingClassNodes, classNodes);
|
|
|
|
surveyTimer.start(1);
|
|
|
|
}
|
2017-10-23 18:21:37 +02:00
|
|
|
//console.timeEnd('dom surveyor/dom layout changed');
|
2016-06-28 15:06:14 +02:00
|
|
|
}
|
2014-09-16 21:39:21 +02:00
|
|
|
};
|
|
|
|
|
2017-10-25 17:42:18 +02:00
|
|
|
var start = function(details) {
|
|
|
|
if ( vAPI.domWatcher instanceof Object === false ) { return; }
|
|
|
|
hostname = details.hostname;
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.domWatcher.addListener(domWatcherInterface);
|
2017-10-25 17:42:18 +02:00
|
|
|
};
|
2015-04-08 01:10:03 +02:00
|
|
|
|
2017-10-25 17:42:18 +02:00
|
|
|
return {
|
|
|
|
start: start
|
|
|
|
};
|
2015-04-08 01:10:03 +02:00
|
|
|
})();
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
2014-09-14 22:20:40 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
// Bootstrapping allows all components of the content script to be launched
|
|
|
|
// if/when needed.
|
2016-08-14 03:45:01 +02:00
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
(function bootstrap() {
|
|
|
|
|
|
|
|
var bootstrapPhase2 = function(ev) {
|
|
|
|
// This can happen on Firefox. For instance:
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1893
|
|
|
|
if ( window.location === null ) { return; }
|
|
|
|
|
|
|
|
if ( ev ) {
|
|
|
|
document.removeEventListener('DOMContentLoaded', bootstrapPhase2);
|
2016-08-13 22:42:58 +02:00
|
|
|
}
|
2016-03-06 16:51:06 +01:00
|
|
|
|
2017-10-25 17:42:18 +02:00
|
|
|
if ( vAPI instanceof Object === false ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.domWatcher.start();
|
2016-08-13 22:42:58 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
|
2017-10-25 17:42:18 +02:00
|
|
|
// Element picker works only in top window for now.
|
|
|
|
if (
|
|
|
|
window !== window.top ||
|
|
|
|
vAPI.domFilterer instanceof Object === false
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
|
|
|
|
// To send mouse coordinates to main process, as the chrome API fails
|
|
|
|
// to provide the mouse position to context menu listeners.
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/1143
|
|
|
|
// Also, find a link under the mouse, to try to avoid confusing new tabs
|
|
|
|
// as nuisance popups.
|
|
|
|
// Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
|
|
|
|
|
|
|
|
var onMouseClick = function(ev) {
|
|
|
|
var elem = ev.target;
|
|
|
|
while ( elem !== null && elem.localName !== 'a' ) {
|
|
|
|
elem = elem.parentElement;
|
2016-08-13 22:42:58 +02:00
|
|
|
}
|
2017-10-21 19:43:46 +02:00
|
|
|
vAPI.messaging.send(
|
|
|
|
'contentscript',
|
|
|
|
{
|
|
|
|
what: 'mouseClick',
|
|
|
|
x: ev.clientX,
|
|
|
|
y: ev.clientY,
|
|
|
|
url: elem !== null && ev.isTrusted !== false ? elem.href : ''
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
2016-08-13 22:42:58 +02:00
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
document.addEventListener('mousedown', onMouseClick, true);
|
2015-01-02 03:14:53 +01:00
|
|
|
|
2016-08-12 14:55:35 +02:00
|
|
|
// https://github.com/gorhill/uMatrix/issues/144
|
|
|
|
vAPI.shutdown.add(function() {
|
|
|
|
document.removeEventListener('mousedown', onMouseClick, true);
|
|
|
|
});
|
2017-10-21 19:43:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
var bootstrapPhase1 = function(response) {
|
|
|
|
// cosmetic filtering engine aka 'cfe'
|
|
|
|
var cfeDetails = response && response.specificCosmeticFilters;
|
|
|
|
if ( !cfeDetails || !cfeDetails.ready ) {
|
|
|
|
vAPI.domWatcher = vAPI.domCollapser = vAPI.domFilterer =
|
|
|
|
vAPI.domSurveyor = vAPI.domIsLoaded = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( response.noCosmeticFiltering ) {
|
|
|
|
vAPI.domFilterer = null;
|
|
|
|
vAPI.domSurveyor = null;
|
|
|
|
} else {
|
|
|
|
var domFilterer = vAPI.domFilterer;
|
|
|
|
if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) {
|
|
|
|
vAPI.domSurveyor = null;
|
|
|
|
}
|
|
|
|
domFilterer.exceptions = cfeDetails.exceptionFilters;
|
2017-10-23 18:21:37 +02:00
|
|
|
domFilterer.hideNodeAttr = cfeDetails.hideNodeAttr;
|
|
|
|
domFilterer.hideNodeStyleSheetInjected =
|
|
|
|
cfeDetails.hideNodeStyleSheetInjected === true;
|
2017-10-21 19:43:46 +02:00
|
|
|
domFilterer.addCSSRule(
|
|
|
|
cfeDetails.declarativeFilters,
|
2017-10-23 18:21:37 +02:00
|
|
|
'display:none!important;'
|
2017-10-21 19:43:46 +02:00
|
|
|
);
|
|
|
|
domFilterer.addCSSRule(
|
|
|
|
cfeDetails.highGenericHideSimple,
|
2017-10-23 15:01:00 +02:00
|
|
|
'display:none!important;',
|
2017-10-23 18:21:37 +02:00
|
|
|
{ type: 'simple', lazy: true }
|
2017-10-21 19:43:46 +02:00
|
|
|
);
|
|
|
|
domFilterer.addCSSRule(
|
|
|
|
cfeDetails.highGenericHideComplex,
|
2017-10-23 15:01:00 +02:00
|
|
|
'display:none!important;',
|
2017-10-23 18:21:37 +02:00
|
|
|
{ type: 'complex', lazy: true }
|
|
|
|
);
|
|
|
|
domFilterer.addCSSRule(
|
|
|
|
cfeDetails.injectedHideFilters,
|
|
|
|
'display:none!important;',
|
|
|
|
{ injected: true }
|
2017-10-21 19:43:46 +02:00
|
|
|
);
|
|
|
|
domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters);
|
|
|
|
}
|
|
|
|
|
2017-10-23 18:21:37 +02:00
|
|
|
if ( cfeDetails.networkFilters.length !== 0 ) {
|
2017-10-22 14:59:29 +02:00
|
|
|
vAPI.userStylesheet.add(
|
2017-10-23 18:21:37 +02:00
|
|
|
cfeDetails.networkFilters + '\n{display:none!important;}');
|
2017-10-22 14:59:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
vAPI.userStylesheet.apply();
|
|
|
|
|
2017-10-23 15:01:00 +02:00
|
|
|
// Library of resources is located at:
|
|
|
|
// https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
|
|
|
|
if ( cfeDetails.scripts ) {
|
|
|
|
// Have the injected script tag remove itself when execution completes:
|
|
|
|
// to keep DOM as clean as possible.
|
|
|
|
var text = cfeDetails.scripts +
|
|
|
|
"\n" +
|
|
|
|
"(function() {\n" +
|
|
|
|
" var c = document.currentScript,\n" +
|
|
|
|
" p = c && c.parentNode;\n" +
|
|
|
|
" if ( p ) {\n" +
|
|
|
|
" p.removeChild(c);\n" +
|
|
|
|
" }\n" +
|
|
|
|
"})();";
|
|
|
|
vAPI.injectScriptlet(document, text);
|
|
|
|
vAPI.injectedScripts = text;
|
2017-10-21 19:43:46 +02:00
|
|
|
}
|
|
|
|
|
2017-10-25 17:42:18 +02:00
|
|
|
if ( vAPI.domSurveyor instanceof Object ) {
|
|
|
|
vAPI.domSurveyor.start(cfeDetails);
|
|
|
|
}
|
|
|
|
|
2017-10-21 19:43:46 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/587
|
|
|
|
// If no filters were found, maybe the script was injected before
|
|
|
|
// uBlock's process was fully initialized. When this happens, pages
|
|
|
|
// won't be cleaned right after browser launch.
|
|
|
|
if (
|
|
|
|
typeof document.readyState === 'string' &&
|
|
|
|
document.readyState !== 'loading'
|
|
|
|
) {
|
|
|
|
bootstrapPhase2();
|
|
|
|
} else {
|
|
|
|
document.addEventListener('DOMContentLoaded', bootstrapPhase2);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// This starts bootstrap process.
|
|
|
|
var url = window.location.href;
|
|
|
|
vAPI.messaging.send(
|
|
|
|
'contentscript',
|
|
|
|
{
|
|
|
|
what: 'retrieveContentScriptParameters',
|
|
|
|
pageURL: url,
|
|
|
|
locationURL: url,
|
|
|
|
isRootFrame: window === window.top
|
|
|
|
},
|
|
|
|
bootstrapPhase1
|
|
|
|
);
|
|
|
|
})();
|
2015-01-02 03:14:53 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
2016-06-28 01:09:04 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
2017-08-17 14:25:02 +02:00
|
|
|
|
|
|
|
} // <<<<<<<< end of HUGE-IF-BLOCK
|