2014-06-23 16:42:43 -06:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2016-03-06 08:51:06 -07:00
|
|
|
uBlock Origin - a browser extension to block requests.
|
2018-12-26 08:45:19 -07:00
|
|
|
Copyright (C) 2014-present Raymond Hill
|
2014-06-23 16:42:43 -06: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-27 17:09:04 -06:00
|
|
|
'use strict';
|
|
|
|
|
2016-08-12 06:55:35 -06:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
+--> domCollapser
|
|
|
|
|
|
|
|
|
|
|
|
|
|
domWatcher--+
|
|
|
|
| +-- domSurveyor
|
|
|
|
| |
|
|
|
|
+--> domFilterer --+-- domLogger
|
|
|
|
|
|
|
|
|
+-- domInspector
|
2016-08-12 06:55:35 -06: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 11:43:46 -06: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 06:55:35 -06:00
|
|
|
If page is whitelisted:
|
|
|
|
- domWatcher: off
|
|
|
|
- domCollapser: off
|
|
|
|
- domFilterer: off
|
|
|
|
- domSurveyor: off
|
2017-10-21 11:43:46 -06:00
|
|
|
- domLogger: off
|
|
|
|
|
2016-08-12 06:55:35 -06: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 11:43:46 -06:00
|
|
|
- domLogger: off
|
2016-08-12 06:55:35 -06:00
|
|
|
|
|
|
|
If generic cosmetic filtering is disabled:
|
|
|
|
- domWatcher: on
|
|
|
|
- domCollapser: on
|
|
|
|
- domFilterer: on
|
|
|
|
- domSurveyor: off
|
2017-10-21 11:43:46 -06: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 06:55:35 -06:00
|
|
|
|
|
|
|
Additionally, the domSurveyor can turn itself off once it decides that
|
|
|
|
it has become pointless (repeatedly not finding new cosmetic filters).
|
2014-06-23 16:42:43 -06:00
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
The domFilterer makes use of platform-dependent user stylesheets[1].
|
|
|
|
|
|
|
|
[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 14:25:36 -07:00
|
|
|
|
2016-08-12 06:55:35 -06:00
|
|
|
*/
|
2014-06-23 16:42:43 -06:00
|
|
|
|
2017-08-17 06:25:02 -06: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
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
// >>>>>>>> start of HUGE-IF-BLOCK
|
|
|
|
if ( typeof vAPI === 'object' && !vAPI.contentScript ) {
|
2017-10-27 12:22:45 -06:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
vAPI.contentScript = true;
|
2017-08-17 06:25:02 -06:00
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-07-24 16:50:12 -06:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-663657508
|
|
|
|
{
|
2020-07-29 05:38:49 -06:00
|
|
|
let context = self;
|
|
|
|
try {
|
|
|
|
while (
|
|
|
|
context !== self.top &&
|
|
|
|
context.location.protocol === 'about:'
|
|
|
|
) {
|
|
|
|
context = context.parent;
|
2020-07-24 16:50:12 -06:00
|
|
|
}
|
2020-07-29 05:38:49 -06:00
|
|
|
} catch(ex) {
|
2020-07-24 16:50:12 -06:00
|
|
|
}
|
2020-07-29 05:38:49 -06:00
|
|
|
vAPI.effectiveSelf = context;
|
2020-07-24 16:50:12 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
vAPI.userStylesheet = {
|
|
|
|
added: new Set(),
|
|
|
|
removed: new Set(),
|
|
|
|
apply: function(callback) {
|
|
|
|
if ( this.added.size === 0 && this.removed.size === 0 ) { return; }
|
|
|
|
vAPI.messaging.send('vapi', {
|
|
|
|
what: 'userCSS',
|
|
|
|
add: Array.from(this.added),
|
|
|
|
remove: Array.from(this.removed),
|
|
|
|
}).then(( ) => {
|
|
|
|
if ( callback instanceof Function === false ) { return; }
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
this.added.clear();
|
|
|
|
this.removed.clear();
|
|
|
|
},
|
|
|
|
add: function(cssText, now) {
|
|
|
|
if ( cssText === '' ) { return; }
|
|
|
|
this.added.add(cssText);
|
|
|
|
if ( now ) { this.apply(); }
|
|
|
|
},
|
|
|
|
remove: function(cssText, now) {
|
|
|
|
if ( cssText === '' ) { return; }
|
|
|
|
this.removed.add(cssText);
|
|
|
|
if ( now ) { this.apply(); }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-06-27 17:09:04 -06:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
2017-10-24 14:38:51 -06: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-27 17:09:04 -06:00
|
|
|
|
2016-11-12 11:38:41 -07:00
|
|
|
// https://github.com/gorhill/uBlock/issues/2147
|
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
vAPI.SafeAnimationFrame = class {
|
|
|
|
constructor(callback) {
|
|
|
|
this.fid = this.tid = undefined;
|
|
|
|
this.callback = callback;
|
|
|
|
}
|
|
|
|
start(delay) {
|
2019-09-30 05:50:35 -06:00
|
|
|
if ( self.vAPI instanceof Object === false ) { return; }
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( delay === undefined ) {
|
2018-12-31 09:50:40 -07:00
|
|
|
if ( this.fid === undefined ) {
|
|
|
|
this.fid = requestAnimationFrame(( ) => { this.onRAF(); } );
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
2018-12-31 09:50:40 -07:00
|
|
|
if ( this.tid === undefined ) {
|
|
|
|
this.tid = vAPI.setTimeout(( ) => { this.onSTO(); }, 20000);
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
2017-10-24 14:38:51 -06:00
|
|
|
return;
|
|
|
|
}
|
2018-12-31 09:50:40 -07:00
|
|
|
if ( this.fid === undefined && this.tid === undefined ) {
|
|
|
|
this.tid = vAPI.setTimeout(( ) => { this.macroToMicro(); }, delay);
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
2020-07-22 08:21:16 -06:00
|
|
|
}
|
|
|
|
clear() {
|
2018-12-31 09:50:40 -07:00
|
|
|
if ( this.fid !== undefined ) {
|
|
|
|
cancelAnimationFrame(this.fid);
|
|
|
|
this.fid = undefined;
|
|
|
|
}
|
|
|
|
if ( this.tid !== undefined ) {
|
|
|
|
clearTimeout(this.tid);
|
|
|
|
this.tid = undefined;
|
|
|
|
}
|
2020-07-22 08:21:16 -06:00
|
|
|
}
|
|
|
|
macroToMicro() {
|
2018-12-31 09:50:40 -07:00
|
|
|
this.tid = undefined;
|
2017-10-24 14:38:51 -06:00
|
|
|
this.start();
|
2020-07-22 08:21:16 -06:00
|
|
|
}
|
|
|
|
onRAF() {
|
2018-12-31 09:50:40 -07:00
|
|
|
if ( this.tid !== undefined ) {
|
|
|
|
clearTimeout(this.tid);
|
|
|
|
this.tid = undefined;
|
|
|
|
}
|
|
|
|
this.fid = undefined;
|
|
|
|
this.callback();
|
2020-07-22 08:21:16 -06:00
|
|
|
}
|
|
|
|
onSTO() {
|
2018-12-31 09:50:40 -07:00
|
|
|
if ( this.fid !== undefined ) {
|
|
|
|
cancelAnimationFrame(this.fid);
|
|
|
|
this.fid = undefined;
|
|
|
|
}
|
|
|
|
this.tid = undefined;
|
|
|
|
this.callback();
|
2020-07-22 08:21:16 -06:00
|
|
|
}
|
2017-08-21 10:04:35 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-05-11 08:40:34 -06:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/552
|
|
|
|
// Listen and report CSP violations so that blocked resources through CSP
|
|
|
|
// are properly reported in the logger.
|
|
|
|
|
|
|
|
{
|
2019-06-24 09:40:14 -06:00
|
|
|
const newEvents = new Set();
|
|
|
|
const allEvents = new Set();
|
2019-05-11 08:40:34 -06:00
|
|
|
let timer;
|
|
|
|
|
|
|
|
const send = function() {
|
2019-09-17 13:15:01 -06:00
|
|
|
vAPI.messaging.send('scriptlets', {
|
|
|
|
what: 'securityPolicyViolation',
|
|
|
|
type: 'net',
|
|
|
|
docURL: document.location.href,
|
|
|
|
violations: Array.from(newEvents),
|
|
|
|
}).then(response => {
|
|
|
|
if ( response === true ) { return; }
|
|
|
|
stop();
|
|
|
|
});
|
2019-06-24 09:40:14 -06:00
|
|
|
for ( const event of newEvents ) {
|
|
|
|
allEvents.add(event);
|
|
|
|
}
|
|
|
|
newEvents.clear();
|
2019-05-11 08:40:34 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
const sendAsync = function() {
|
|
|
|
if ( timer !== undefined ) { return; }
|
|
|
|
timer = self.requestIdleCallback(
|
|
|
|
( ) => { timer = undefined; send(); },
|
2019-09-17 13:15:01 -06:00
|
|
|
{ timeout: 2063 }
|
2019-05-11 08:40:34 -06:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const listener = function(ev) {
|
|
|
|
if ( ev.isTrusted !== true ) { return; }
|
|
|
|
if ( ev.disposition !== 'enforce' ) { return; }
|
2019-06-24 09:40:14 -06:00
|
|
|
const json = JSON.stringify({
|
2019-05-11 08:40:34 -06:00
|
|
|
url: ev.blockedURL || ev.blockedURI,
|
|
|
|
policy: ev.originalPolicy,
|
|
|
|
directive: ev.effectiveDirective || ev.violatedDirective,
|
2019-06-24 09:40:14 -06:00
|
|
|
});
|
|
|
|
if ( allEvents.has(json) ) { return; }
|
|
|
|
newEvents.add(json);
|
2019-05-11 08:40:34 -06:00
|
|
|
sendAsync();
|
|
|
|
};
|
|
|
|
|
|
|
|
const stop = function() {
|
2019-06-24 09:40:14 -06:00
|
|
|
newEvents.clear();
|
|
|
|
allEvents.clear();
|
2019-05-11 08:40:34 -06:00
|
|
|
if ( timer !== undefined ) {
|
|
|
|
self.cancelIdleCallback(timer);
|
|
|
|
timer = undefined;
|
|
|
|
}
|
|
|
|
document.removeEventListener('securitypolicyviolation', listener);
|
|
|
|
vAPI.shutdown.remove(stop);
|
|
|
|
};
|
|
|
|
|
|
|
|
document.addEventListener('securitypolicyviolation', listener);
|
|
|
|
vAPI.shutdown.add(stop);
|
|
|
|
|
|
|
|
// We need to call at least once to find out whether we really need to
|
|
|
|
// listen to CSP violations.
|
|
|
|
sendAsync();
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
// vAPI.domWatcher
|
|
|
|
|
|
|
|
{
|
2019-10-31 09:07:11 -06:00
|
|
|
vAPI.domMutationTime = Date.now();
|
2017-07-23 07:56:43 -06:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const addedNodeLists = [];
|
|
|
|
const removedNodeLists = [];
|
|
|
|
const addedNodes = [];
|
|
|
|
const ignoreTags = new Set([ 'br', 'head', 'link', 'meta', 'script', 'style' ]);
|
|
|
|
const listeners = [];
|
|
|
|
|
2020-07-29 05:38:49 -06:00
|
|
|
let domLayoutObserver;
|
|
|
|
let listenerIterator = [];
|
|
|
|
let listenerIteratorDirty = false;
|
|
|
|
let removedNodes = false;
|
|
|
|
let safeObserverHandlerTimer;
|
2016-12-16 14:25:36 -07:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const safeObserverHandler = function() {
|
2019-10-31 09:07:11 -06:00
|
|
|
let i = addedNodeLists.length;
|
2017-10-21 11:43:46 -06:00
|
|
|
while ( i-- ) {
|
2018-12-26 08:45:19 -07:00
|
|
|
const nodeList = addedNodeLists[i];
|
|
|
|
let iNode = nodeList.length;
|
2017-10-21 11:43:46 -06:00
|
|
|
while ( iNode-- ) {
|
2018-12-26 08:45:19 -07:00
|
|
|
const node = nodeList[iNode];
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( node.nodeType !== 1 ) { continue; }
|
|
|
|
if ( ignoreTags.has(node.localName) ) { continue; }
|
|
|
|
if ( node.parentElement === null ) { continue; }
|
2019-10-31 09:07:11 -06:00
|
|
|
addedNodes.push(node);
|
2016-12-16 14:25:36 -07:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
|
|
|
addedNodeLists.length = 0;
|
|
|
|
i = removedNodeLists.length;
|
|
|
|
while ( i-- && removedNodes === false ) {
|
2018-12-26 08:45:19 -07:00
|
|
|
const nodeList = removedNodeLists[i];
|
|
|
|
let iNode = nodeList.length;
|
2017-10-21 11:43:46 -06:00
|
|
|
while ( iNode-- ) {
|
|
|
|
if ( nodeList[iNode].nodeType !== 1 ) { continue; }
|
|
|
|
removedNodes = true;
|
|
|
|
break;
|
2016-12-16 14:25:36 -07:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
|
|
|
removedNodeLists.length = 0;
|
|
|
|
if ( addedNodes.length === 0 && removedNodes === false ) { return; }
|
2018-12-26 08:45:19 -07:00
|
|
|
for ( const listener of getListenerIterator() ) {
|
2019-09-30 16:21:24 -06:00
|
|
|
try { listener.onDOMChanged(addedNodes, removedNodes); }
|
|
|
|
catch (ex) { }
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
|
|
|
addedNodes.length = 0;
|
|
|
|
removedNodes = false;
|
2019-10-31 09:07:11 -06:00
|
|
|
vAPI.domMutationTime = Date.now();
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/205
|
|
|
|
// Do not handle added node directly from within mutation observer.
|
2018-12-26 08:45:19 -07:00
|
|
|
const observerHandler = function(mutations) {
|
|
|
|
let i = mutations.length;
|
2017-10-21 11:43:46 -06:00
|
|
|
while ( i-- ) {
|
2018-12-26 08:45:19 -07:00
|
|
|
const mutation = mutations[i];
|
|
|
|
let nodeList = mutation.addedNodes;
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( nodeList.length !== 0 ) {
|
|
|
|
addedNodeLists.push(nodeList);
|
2016-12-16 14:25:36 -07:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
nodeList = mutation.removedNodes;
|
|
|
|
if ( nodeList.length !== 0 ) {
|
|
|
|
removedNodeLists.push(nodeList);
|
2016-12-16 14:25:36 -07:00
|
|
|
}
|
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( addedNodeLists.length !== 0 || removedNodes ) {
|
2017-12-10 13:03:03 -07:00
|
|
|
safeObserverHandlerTimer.start(
|
|
|
|
addedNodeLists.length < 100 ? 1 : undefined
|
|
|
|
);
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
2016-12-16 14:25:36 -07:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const startMutationObserver = function() {
|
2020-07-29 05:38:49 -06:00
|
|
|
if ( domLayoutObserver !== undefined ) { return; }
|
2017-10-21 11:43:46 -06:00
|
|
|
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 08:47:32 -07:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const stopMutationObserver = function() {
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( domLayoutObserver === undefined ) { return; }
|
|
|
|
cleanup();
|
|
|
|
vAPI.shutdown.remove(cleanup);
|
|
|
|
};
|
2016-12-15 08:47:32 -07:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const getListenerIterator = function() {
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( listenerIteratorDirty ) {
|
|
|
|
listenerIterator = listeners.slice();
|
|
|
|
listenerIteratorDirty = false;
|
2017-03-12 08:22:46 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
return listenerIterator;
|
2016-12-15 08:47:32 -07:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const addListener = function(listener) {
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( listeners.indexOf(listener) !== -1 ) { return; }
|
|
|
|
listeners.push(listener);
|
|
|
|
listenerIteratorDirty = true;
|
2020-07-29 05:38:49 -06:00
|
|
|
if ( domLayoutObserver === undefined ) { return; }
|
2019-09-30 16:21:24 -06:00
|
|
|
try { listener.onDOMCreated(); }
|
|
|
|
catch (ex) { }
|
2017-10-25 09:42:18 -06:00
|
|
|
startMutationObserver();
|
2016-12-15 08:47:32 -07:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const removeListener = function(listener) {
|
|
|
|
const pos = listeners.indexOf(listener);
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( pos === -1 ) { return; }
|
|
|
|
listeners.splice(pos, 1);
|
|
|
|
listenerIteratorDirty = true;
|
|
|
|
if ( listeners.length === 0 ) {
|
|
|
|
stopMutationObserver();
|
2017-03-12 08:22:46 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const cleanup = function() {
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( domLayoutObserver !== undefined ) {
|
|
|
|
domLayoutObserver.disconnect();
|
2020-10-31 08:18:42 -06:00
|
|
|
domLayoutObserver = undefined;
|
2016-12-15 08:47:32 -07:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( safeObserverHandlerTimer !== undefined ) {
|
|
|
|
safeObserverHandlerTimer.clear();
|
|
|
|
safeObserverHandlerTimer = undefined;
|
2016-12-15 08:47:32 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const start = function() {
|
|
|
|
for ( const listener of getListenerIterator() ) {
|
2019-09-30 16:21:24 -06:00
|
|
|
try { listener.onDOMCreated(); }
|
|
|
|
catch (ex) { }
|
2016-12-15 08:47:32 -07:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
startMutationObserver();
|
|
|
|
};
|
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
vAPI.domWatcher = { start, addListener, removeListener };
|
|
|
|
}
|
2016-12-15 08:47:32 -07:00
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
2016-12-15 08:47:32 -07:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
vAPI.injectScriptlet = function(doc, text) {
|
|
|
|
if ( !doc ) { return; }
|
2018-05-18 08:19:14 -06:00
|
|
|
let script;
|
2017-10-21 11:43:46 -06:00
|
|
|
try {
|
2018-05-18 08:19:14 -06:00
|
|
|
script = doc.createElement('script');
|
2017-10-21 11:43:46 -06:00
|
|
|
script.appendChild(doc.createTextNode(text));
|
|
|
|
(doc.head || doc.documentElement).appendChild(script);
|
|
|
|
} catch (ex) {
|
2016-08-06 10:09:18 -06:00
|
|
|
}
|
2018-06-01 16:41:27 -06:00
|
|
|
if ( script ) {
|
|
|
|
if ( script.parentNode ) {
|
|
|
|
script.parentNode.removeChild(script);
|
|
|
|
}
|
|
|
|
script.textContent = '';
|
2018-05-18 08:19:14 -06:00
|
|
|
}
|
2016-08-06 10:09:18 -06:00
|
|
|
};
|
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/*******************************************************************************
|
2016-08-06 10:09:18 -06:00
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
The DOM filterer is the heart of uBO's cosmetic filtering.
|
2016-12-25 14:56:39 -07:00
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
DOMFilterer: adds procedural cosmetic filtering
|
2016-12-25 14:56:39 -07:00
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
*/
|
2016-12-25 14:56:39 -07:00
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
{
|
2020-09-07 06:28:01 -06:00
|
|
|
vAPI.hideStyle = 'display:none!important;';
|
|
|
|
|
|
|
|
// TODO: Experiment/evaluate loading procedural operator code using an
|
|
|
|
// on demand approach.
|
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
// 'P' stands for 'Procedural'
|
2016-08-06 10:09:18 -06:00
|
|
|
|
2019-04-30 07:02:14 -06:00
|
|
|
const PSelectorHasTextTask = class {
|
|
|
|
constructor(task) {
|
|
|
|
let arg0 = task[1], arg1;
|
|
|
|
if ( Array.isArray(task[1]) ) {
|
|
|
|
arg1 = arg0[1]; arg0 = arg0[0];
|
|
|
|
}
|
|
|
|
this.needle = new RegExp(arg0, arg1);
|
2017-12-30 16:55:01 -07:00
|
|
|
}
|
2019-06-23 06:05:53 -06:00
|
|
|
transpose(node, output) {
|
|
|
|
if ( this.needle.test(node.textContent) ) {
|
|
|
|
output.push(node);
|
2016-07-09 17:21:46 -06:00
|
|
|
}
|
2016-06-27 17:09:04 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
2016-06-28 17:45:11 -06:00
|
|
|
|
2019-04-30 07:02:14 -06:00
|
|
|
const PSelectorIfTask = class {
|
|
|
|
constructor(task) {
|
|
|
|
this.pselector = new PSelector(task[1]);
|
|
|
|
}
|
2019-06-23 06:05:53 -06:00
|
|
|
transpose(node, output) {
|
|
|
|
if ( this.pselector.test(node) === this.target ) {
|
|
|
|
output.push(node);
|
2016-07-09 17:21:46 -06:00
|
|
|
}
|
2016-06-28 17:45:11 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
2019-04-30 07:02:14 -06:00
|
|
|
PSelectorIfTask.prototype.target = true;
|
2016-06-28 17:45:11 -06:00
|
|
|
|
2019-04-30 07:02:14 -06:00
|
|
|
const PSelectorIfNotTask = class extends PSelectorIfTask {
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
2019-06-23 06:05:53 -06:00
|
|
|
PSelectorIfNotTask.prototype.target = false;
|
2019-04-30 07:02:14 -06:00
|
|
|
|
|
|
|
const PSelectorMatchesCSSTask = class {
|
|
|
|
constructor(task) {
|
|
|
|
this.name = task[1].name;
|
|
|
|
let arg0 = task[1].value, arg1;
|
|
|
|
if ( Array.isArray(arg0) ) {
|
|
|
|
arg1 = arg0[1]; arg0 = arg0[0];
|
|
|
|
}
|
|
|
|
this.value = new RegExp(arg0, arg1);
|
|
|
|
}
|
2019-06-23 06:05:53 -06:00
|
|
|
transpose(node, output) {
|
|
|
|
const style = window.getComputedStyle(node, this.pseudo);
|
|
|
|
if ( style !== null && this.value.test(style[this.name]) ) {
|
|
|
|
output.push(node);
|
2016-09-22 10:18:01 -06:00
|
|
|
}
|
2016-07-12 11:29:30 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
2019-04-30 07:02:14 -06:00
|
|
|
PSelectorMatchesCSSTask.prototype.pseudo = null;
|
2016-07-12 11:29:30 -06:00
|
|
|
|
2019-04-30 07:02:14 -06:00
|
|
|
const PSelectorMatchesCSSAfterTask = class extends PSelectorMatchesCSSTask {
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
2019-06-23 06:05:53 -06:00
|
|
|
PSelectorMatchesCSSAfterTask.prototype.pseudo = ':after';
|
2016-09-24 12:42:31 -06:00
|
|
|
|
2019-04-30 07:02:14 -06:00
|
|
|
const PSelectorMatchesCSSBeforeTask = class extends PSelectorMatchesCSSTask {
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
2019-06-23 06:05:53 -06:00
|
|
|
PSelectorMatchesCSSBeforeTask.prototype.pseudo = ':before';
|
2016-06-28 17:45:11 -06:00
|
|
|
|
2019-06-20 12:11:54 -06:00
|
|
|
const PSelectorMinTextLengthTask = class {
|
|
|
|
constructor(task) {
|
|
|
|
this.min = task[1];
|
|
|
|
}
|
2019-06-23 06:05:53 -06:00
|
|
|
transpose(node, output) {
|
|
|
|
if ( node.textContent.length >= this.min ) {
|
|
|
|
output.push(node);
|
2019-06-20 12:11:54 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-04-30 07:02:14 -06:00
|
|
|
const PSelectorSpathTask = class {
|
|
|
|
constructor(task) {
|
|
|
|
this.spath = task[1];
|
2020-07-30 09:58:49 -06:00
|
|
|
this.nth = /^(?:\s*[+~]|:)/.test(this.spath);
|
2020-08-04 09:30:04 -06:00
|
|
|
if ( this.nth ) { return; }
|
|
|
|
if ( /^\s*>/.test(this.spath) ) {
|
|
|
|
this.spath = `:scope ${this.spath.trim()}`;
|
|
|
|
}
|
2019-04-30 07:02:14 -06:00
|
|
|
}
|
2020-07-30 09:58:49 -06:00
|
|
|
qsa(node) {
|
|
|
|
if ( this.nth === false ) {
|
|
|
|
return node.querySelectorAll(this.spath);
|
|
|
|
}
|
2019-06-23 06:05:53 -06:00
|
|
|
const parent = node.parentElement;
|
|
|
|
if ( parent === null ) { return; }
|
|
|
|
let pos = 1;
|
|
|
|
for (;;) {
|
|
|
|
node = node.previousElementSibling;
|
|
|
|
if ( node === null ) { break; }
|
|
|
|
pos += 1;
|
|
|
|
}
|
2020-07-30 09:58:49 -06:00
|
|
|
return parent.querySelectorAll(
|
2019-06-23 06:05:53 -06:00
|
|
|
`:scope > :nth-child(${pos})${this.spath}`
|
|
|
|
);
|
2020-07-30 09:58:49 -06:00
|
|
|
}
|
|
|
|
transpose(node, output) {
|
|
|
|
const nodes = this.qsa(node);
|
|
|
|
if ( nodes === undefined ) { return; }
|
2019-06-23 06:05:53 -06:00
|
|
|
for ( const node of nodes ) {
|
|
|
|
output.push(node);
|
2019-04-30 07:02:14 -06:00
|
|
|
}
|
2018-12-26 08:45:19 -07:00
|
|
|
}
|
|
|
|
};
|
2019-04-30 07:02:14 -06:00
|
|
|
|
2020-03-07 12:25:06 -07:00
|
|
|
const PSelectorUpwardTask = class {
|
|
|
|
constructor(task) {
|
|
|
|
const arg = task[1];
|
|
|
|
if ( typeof arg === 'number' ) {
|
|
|
|
this.i = arg;
|
|
|
|
} else {
|
|
|
|
this.s = arg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
transpose(node, output) {
|
|
|
|
if ( this.s !== '' ) {
|
|
|
|
const parent = node.parentElement;
|
|
|
|
if ( parent === null ) { return; }
|
|
|
|
node = parent.closest(this.s);
|
|
|
|
if ( node === null ) { return; }
|
|
|
|
} else {
|
|
|
|
let nth = this.i;
|
|
|
|
for (;;) {
|
|
|
|
node = node.parentElement;
|
|
|
|
if ( node === null ) { return; }
|
|
|
|
nth -= 1;
|
|
|
|
if ( nth === 0 ) { break; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
output.push(node);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
PSelectorUpwardTask.prototype.i = 0;
|
|
|
|
PSelectorUpwardTask.prototype.s = '';
|
|
|
|
|
2019-04-30 07:02:14 -06:00
|
|
|
const PSelectorWatchAttrs = class {
|
|
|
|
constructor(task) {
|
|
|
|
this.observer = null;
|
|
|
|
this.observed = new WeakSet();
|
|
|
|
this.observerOptions = {
|
|
|
|
attributes: true,
|
|
|
|
subtree: true,
|
|
|
|
};
|
|
|
|
const attrs = task[1];
|
|
|
|
if ( Array.isArray(attrs) && attrs.length !== 0 ) {
|
|
|
|
this.observerOptions.attributeFilter = task[1];
|
|
|
|
}
|
2018-12-26 08:45:19 -07:00
|
|
|
}
|
2019-04-30 07:02:14 -06:00
|
|
|
// TODO: Is it worth trying to re-apply only the current selector?
|
|
|
|
handler() {
|
|
|
|
const filterer =
|
|
|
|
vAPI.domFilterer && vAPI.domFilterer.proceduralFilterer;
|
|
|
|
if ( filterer instanceof Object ) {
|
|
|
|
filterer.onDOMChanged([ null ]);
|
|
|
|
}
|
2018-12-26 08:45:19 -07:00
|
|
|
}
|
2019-06-23 06:05:53 -06:00
|
|
|
transpose(node, output) {
|
|
|
|
output.push(node);
|
|
|
|
if ( this.observed.has(node) ) { return; }
|
2019-04-30 07:02:14 -06:00
|
|
|
if ( this.observer === null ) {
|
|
|
|
this.observer = new MutationObserver(this.handler);
|
|
|
|
}
|
2019-06-23 06:05:53 -06:00
|
|
|
this.observer.observe(node, this.observerOptions);
|
|
|
|
this.observed.add(node);
|
2018-12-26 08:45:19 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-04-30 07:02:14 -06:00
|
|
|
const PSelectorXpathTask = class {
|
|
|
|
constructor(task) {
|
|
|
|
this.xpe = document.createExpression(task[1], null);
|
|
|
|
this.xpr = null;
|
|
|
|
}
|
2019-06-23 06:05:53 -06:00
|
|
|
transpose(node, output) {
|
|
|
|
this.xpr = this.xpe.evaluate(
|
|
|
|
node,
|
|
|
|
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
|
|
|
|
this.xpr
|
|
|
|
);
|
|
|
|
let j = this.xpr.snapshotLength;
|
|
|
|
while ( j-- ) {
|
|
|
|
const node = this.xpr.snapshotItem(j);
|
|
|
|
if ( node.nodeType === 1 ) {
|
|
|
|
output.push(node);
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
|
|
|
}
|
2016-06-28 17:45:11 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
2016-06-28 17:45:11 -06:00
|
|
|
|
2019-04-30 07:02:14 -06:00
|
|
|
const PSelector = class {
|
|
|
|
constructor(o) {
|
|
|
|
if ( PSelector.prototype.operatorToTaskMap === undefined ) {
|
|
|
|
PSelector.prototype.operatorToTaskMap = new Map([
|
|
|
|
[ ':has', PSelectorIfTask ],
|
|
|
|
[ ':has-text', PSelectorHasTextTask ],
|
|
|
|
[ ':if', PSelectorIfTask ],
|
|
|
|
[ ':if-not', PSelectorIfNotTask ],
|
|
|
|
[ ':matches-css', PSelectorMatchesCSSTask ],
|
|
|
|
[ ':matches-css-after', PSelectorMatchesCSSAfterTask ],
|
|
|
|
[ ':matches-css-before', PSelectorMatchesCSSBeforeTask ],
|
2019-06-20 12:11:54 -06:00
|
|
|
[ ':min-text-length', PSelectorMinTextLengthTask ],
|
2019-04-30 07:02:14 -06:00
|
|
|
[ ':not', PSelectorIfNotTask ],
|
2020-03-07 12:25:06 -07:00
|
|
|
[ ':nth-ancestor', PSelectorUpwardTask ],
|
2019-04-30 07:02:14 -06:00
|
|
|
[ ':spath', PSelectorSpathTask ],
|
2020-03-07 12:25:06 -07:00
|
|
|
[ ':upward', PSelectorUpwardTask ],
|
2019-06-23 06:05:53 -06:00
|
|
|
[ ':watch-attr', PSelectorWatchAttrs ],
|
2019-04-30 07:02:14 -06:00
|
|
|
[ ':xpath', PSelectorXpathTask ],
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
this.raw = o.raw;
|
|
|
|
this.selector = o.selector;
|
|
|
|
this.tasks = [];
|
|
|
|
const tasks = o.tasks;
|
2020-03-07 12:25:06 -07:00
|
|
|
if ( Array.isArray(tasks) ) {
|
|
|
|
for ( const task of tasks ) {
|
|
|
|
this.tasks.push(
|
|
|
|
new (this.operatorToTaskMap.get(task[0]))(task)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2016-08-14 06:51:52 -06:00
|
|
|
}
|
2019-04-30 07:02:14 -06:00
|
|
|
prime(input) {
|
|
|
|
const root = input || document;
|
2019-06-29 12:07:54 -06:00
|
|
|
if ( this.selector === '' ) { return [ root ]; }
|
2020-03-07 12:25:06 -07:00
|
|
|
return Array.from(root.querySelectorAll(this.selector));
|
2016-07-09 17:21:46 -06:00
|
|
|
}
|
2019-04-30 07:02:14 -06:00
|
|
|
exec(input) {
|
|
|
|
let nodes = this.prime(input);
|
2018-12-14 15:55:22 -07:00
|
|
|
for ( const task of this.tasks ) {
|
2019-04-30 07:02:14 -06:00
|
|
|
if ( nodes.length === 0 ) { break; }
|
2019-06-23 06:05:53 -06:00
|
|
|
const transposed = [];
|
|
|
|
for ( const node of nodes ) {
|
|
|
|
task.transpose(node, transposed);
|
|
|
|
}
|
|
|
|
nodes = transposed;
|
2019-04-30 07:02:14 -06:00
|
|
|
}
|
|
|
|
return nodes;
|
|
|
|
}
|
|
|
|
test(input) {
|
|
|
|
const nodes = this.prime(input);
|
|
|
|
for ( const node of nodes ) {
|
2019-06-23 06:05:53 -06:00
|
|
|
let output = [ node ];
|
2019-04-30 07:02:14 -06:00
|
|
|
for ( const task of this.tasks ) {
|
2019-06-23 06:05:53 -06:00
|
|
|
const transposed = [];
|
|
|
|
for ( const node of output ) {
|
|
|
|
task.transpose(node, transposed);
|
|
|
|
}
|
|
|
|
output = transposed;
|
|
|
|
if ( output.length === 0 ) { break; }
|
2019-04-30 07:02:14 -06:00
|
|
|
}
|
2019-06-23 06:05:53 -06:00
|
|
|
if ( output.length !== 0 ) { return true; }
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
2019-04-30 07:02:14 -06:00
|
|
|
return false;
|
2016-07-09 17:21:46 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
2019-04-30 07:02:14 -06:00
|
|
|
PSelector.prototype.operatorToTaskMap = undefined;
|
2016-11-12 11:38:41 -07:00
|
|
|
|
2020-09-07 06:28:01 -06:00
|
|
|
const PSelectorRoot = class extends PSelector {
|
|
|
|
constructor(o, styleToken) {
|
|
|
|
super(o);
|
|
|
|
this.budget = 200; // I arbitrary picked a 1/5 second
|
|
|
|
this.raw = o.raw;
|
|
|
|
this.cost = 0;
|
|
|
|
this.lastAllowanceTime = 0;
|
|
|
|
this.styleToken = styleToken;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
PSelectorRoot.prototype.hit = false;
|
|
|
|
|
2019-05-16 15:22:20 -06:00
|
|
|
const DOMProceduralFilterer = class {
|
|
|
|
constructor(domFilterer) {
|
|
|
|
this.domFilterer = domFilterer;
|
|
|
|
this.domIsReady = false;
|
|
|
|
this.domIsWatched = false;
|
|
|
|
this.mustApplySelectors = false;
|
|
|
|
this.selectors = new Map();
|
2020-09-07 06:28:01 -06:00
|
|
|
this.masterToken = vAPI.randomToken();
|
|
|
|
this.styleTokenMap = new Map();
|
|
|
|
this.styledNodes = new Set();
|
2020-07-24 17:08:48 -06:00
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
|
|
|
vAPI.domWatcher.addListener(this);
|
|
|
|
}
|
2019-05-16 15:22:20 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
|
2020-09-07 06:28:01 -06:00
|
|
|
addProceduralSelectors(selectors) {
|
2018-12-14 15:55:22 -07:00
|
|
|
const addedSelectors = [];
|
|
|
|
let mustCommit = this.domIsWatched;
|
2020-09-24 08:53:18 -06:00
|
|
|
for ( const selector of selectors ) {
|
|
|
|
if ( this.selectors.has(selector.raw) ) { continue; }
|
2020-09-07 06:28:01 -06:00
|
|
|
let style, styleToken;
|
2020-09-24 08:53:18 -06:00
|
|
|
if ( selector.action === undefined ) {
|
2020-09-07 06:28:01 -06:00
|
|
|
style = vAPI.hideStyle;
|
2020-09-24 08:53:18 -06:00
|
|
|
} else if ( selector.action[0] === ':style' ) {
|
|
|
|
style = selector.action[1];
|
2020-09-07 06:28:01 -06:00
|
|
|
}
|
|
|
|
if ( style !== undefined ) {
|
|
|
|
styleToken = this.styleTokenFromStyle(style);
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
2020-09-24 08:53:18 -06:00
|
|
|
const pselector = new PSelectorRoot(selector, styleToken);
|
|
|
|
this.selectors.set(selector.raw, pselector);
|
2020-09-07 06:28:01 -06:00
|
|
|
addedSelectors.push(pselector);
|
|
|
|
mustCommit = true;
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
|
|
|
if ( mustCommit === false ) { return; }
|
2018-12-14 15:55:22 -07:00
|
|
|
this.mustApplySelectors = this.selectors.size !== 0;
|
2017-10-21 11:43:46 -06:00
|
|
|
this.domFilterer.commit();
|
2017-11-14 13:03:20 -07:00
|
|
|
if ( this.domFilterer.hasListeners() ) {
|
|
|
|
this.domFilterer.triggerListeners({
|
2018-12-14 15:55:22 -07:00
|
|
|
procedural: addedSelectors
|
2017-11-14 13:03:20 -07:00
|
|
|
});
|
|
|
|
}
|
2019-05-16 15:22:20 -06:00
|
|
|
}
|
2015-01-13 13:52:15 -07:00
|
|
|
|
2019-05-16 15:22:20 -06:00
|
|
|
commitNow() {
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( this.selectors.size === 0 || this.domIsReady === false ) {
|
|
|
|
return;
|
|
|
|
}
|
2014-06-23 16:42:43 -06:00
|
|
|
|
2018-12-14 15:55:22 -07:00
|
|
|
this.mustApplySelectors = false;
|
2016-06-27 17:09:04 -06:00
|
|
|
|
2017-10-31 04:47:39 -06:00
|
|
|
//console.time('procedural selectors/dom layout changed');
|
2016-06-27 17:09:04 -06:00
|
|
|
|
2018-12-14 15:55:22 -07:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/341
|
|
|
|
// Be ready to unhide nodes which no longer matches any of
|
|
|
|
// the procedural selectors.
|
2020-09-07 06:28:01 -06:00
|
|
|
const toUnstyle = this.styledNodes;
|
|
|
|
this.styledNodes = new Set();
|
2016-06-27 17:09:04 -06:00
|
|
|
|
2018-12-14 15:55:22 -07:00
|
|
|
let t0 = Date.now();
|
2016-06-27 17:09:04 -06:00
|
|
|
|
2020-03-07 12:25:06 -07:00
|
|
|
for ( const pselector of this.selectors.values() ) {
|
2018-12-14 15:55:22 -07:00
|
|
|
const allowance = Math.floor((t0 - pselector.lastAllowanceTime) / 2000);
|
2017-12-10 13:03:03 -07:00
|
|
|
if ( allowance >= 1 ) {
|
|
|
|
pselector.budget += allowance * 50;
|
|
|
|
if ( pselector.budget > 200 ) { pselector.budget = 200; }
|
|
|
|
pselector.lastAllowanceTime = t0;
|
|
|
|
}
|
2017-10-24 14:38:51 -06:00
|
|
|
if ( pselector.budget <= 0 ) { continue; }
|
2018-12-14 15:55:22 -07:00
|
|
|
const nodes = pselector.exec();
|
|
|
|
const t1 = Date.now();
|
2017-12-10 13:03:03 -07:00
|
|
|
pselector.budget += t0 - t1;
|
|
|
|
if ( pselector.budget < -500 ) {
|
2017-12-13 06:02:55 -07:00
|
|
|
console.info('uBO: disabling %s', pselector.raw);
|
2017-12-10 13:03:03 -07:00
|
|
|
pselector.budget = -0x7FFFFFFF;
|
2017-10-24 14:38:51 -06:00
|
|
|
}
|
2017-12-10 13:03:03 -07:00
|
|
|
t0 = t1;
|
2020-03-07 12:25:06 -07:00
|
|
|
if ( nodes.length === 0 ) { continue; }
|
|
|
|
pselector.hit = true;
|
2020-09-07 06:28:01 -06:00
|
|
|
this.styleNodes(nodes, pselector.styleToken);
|
2016-07-09 17:21:46 -06:00
|
|
|
}
|
2015-01-01 18:58:19 -07:00
|
|
|
|
2020-09-07 06:28:01 -06:00
|
|
|
this.unstyleNodes(toUnstyle);
|
2017-10-31 04:47:39 -06:00
|
|
|
//console.timeEnd('procedural selectors/dom layout changed');
|
2019-05-16 15:22:20 -06:00
|
|
|
}
|
2016-06-27 17:09:04 -06:00
|
|
|
|
2020-09-07 06:28:01 -06:00
|
|
|
styleTokenFromStyle(style) {
|
|
|
|
if ( style === undefined ) { return; }
|
|
|
|
let styleToken = this.styleTokenMap.get(style);
|
|
|
|
if ( styleToken !== undefined ) { return styleToken; }
|
|
|
|
styleToken = vAPI.randomToken();
|
|
|
|
this.styleTokenMap.set(style, styleToken);
|
|
|
|
this.domFilterer.addCSSRule(
|
|
|
|
`[${this.masterToken}][${styleToken}]`,
|
|
|
|
style,
|
2020-10-13 05:19:06 -06:00
|
|
|
{ silent: true, mustInject: true }
|
2020-09-07 06:28:01 -06:00
|
|
|
);
|
|
|
|
return styleToken;
|
|
|
|
}
|
|
|
|
|
|
|
|
styleNodes(nodes, styleToken) {
|
|
|
|
if ( styleToken === undefined ) {
|
|
|
|
for ( const node of nodes ) {
|
|
|
|
node.textContent = '';
|
|
|
|
node.remove();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2020-03-07 12:25:06 -07:00
|
|
|
for ( const node of nodes ) {
|
2020-09-07 06:28:01 -06:00
|
|
|
node.setAttribute(this.masterToken, '');
|
|
|
|
node.setAttribute(styleToken, '');
|
2020-03-07 12:25:06 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-07 06:28:01 -06:00
|
|
|
// TODO: Current assumption is one style per hit element. Could be an
|
|
|
|
// issue if an element has multiple styling and one styling is
|
|
|
|
// brough back. Possibly too rare to care about this for now.
|
|
|
|
unstyleNodes(nodes) {
|
2020-03-07 12:25:06 -07:00
|
|
|
for ( const node of nodes ) {
|
2020-09-07 06:28:01 -06:00
|
|
|
if ( this.styledNodes.has(node) ) { continue; }
|
|
|
|
node.removeAttribute(this.masterToken);
|
2020-03-07 12:25:06 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-16 15:22:20 -06:00
|
|
|
createProceduralFilter(o) {
|
2020-09-07 06:28:01 -06:00
|
|
|
return new PSelectorRoot(o);
|
2019-05-16 15:22:20 -06:00
|
|
|
}
|
2016-06-27 17:09:04 -06:00
|
|
|
|
2019-05-16 15:22:20 -06:00
|
|
|
onDOMCreated() {
|
2017-10-21 11:43:46 -06:00
|
|
|
this.domIsReady = true;
|
2020-07-24 17:08:48 -06:00
|
|
|
this.domFilterer.commit();
|
2019-05-16 15:22:20 -06:00
|
|
|
}
|
2014-06-23 16:42:43 -06:00
|
|
|
|
2019-05-16 15:22:20 -06:00
|
|
|
onDOMChanged(addedNodes, removedNodes) {
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( this.selectors.size === 0 ) { return; }
|
2018-12-14 15:55:22 -07:00
|
|
|
this.mustApplySelectors =
|
|
|
|
this.mustApplySelectors ||
|
|
|
|
addedNodes.length !== 0 ||
|
|
|
|
removedNodes;
|
2017-10-21 11:43:46 -06:00
|
|
|
this.domFilterer.commit();
|
|
|
|
}
|
|
|
|
};
|
2016-08-12 06:55:35 -06:00
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
vAPI.DOMFilterer = class {
|
2019-05-16 11:44:49 -06:00
|
|
|
constructor() {
|
2020-07-22 08:21:16 -06:00
|
|
|
this.commitTimer = new vAPI.SafeAnimationFrame(
|
|
|
|
( ) => { this.commitNow(); }
|
|
|
|
);
|
|
|
|
this.domIsReady = document.readyState !== 'loading';
|
|
|
|
this.disabled = false;
|
|
|
|
this.listeners = [];
|
|
|
|
this.filterset = new Set();
|
|
|
|
this.exceptedCSSRules = [];
|
2019-05-16 11:44:49 -06:00
|
|
|
this.exceptions = [];
|
2020-07-24 17:08:48 -06:00
|
|
|
this.proceduralFilterer = null;
|
2020-07-22 08:21:16 -06:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/167
|
|
|
|
// By the time the DOMContentLoaded is fired, the content script might
|
|
|
|
// have been disconnected from the background page. Unclear why this
|
|
|
|
// would happen, so far seems to be a Chromium-specific behavior at
|
|
|
|
// launch time.
|
|
|
|
if ( this.domIsReady !== true ) {
|
|
|
|
document.addEventListener('DOMContentLoaded', ( ) => {
|
|
|
|
if ( vAPI instanceof Object === false ) { return; }
|
|
|
|
this.domIsReady = true;
|
|
|
|
this.commit();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addCSSRule(selectors, declarations, details = {}) {
|
|
|
|
if ( selectors === undefined ) { return; }
|
|
|
|
const selectorsStr = Array.isArray(selectors)
|
2020-10-13 05:19:06 -06:00
|
|
|
? selectors.join(',\n')
|
|
|
|
: selectors;
|
2020-07-22 08:21:16 -06:00
|
|
|
if ( selectorsStr.length === 0 ) { return; }
|
2020-10-13 05:19:06 -06:00
|
|
|
this.filterset.add({ selectors: selectorsStr, declarations });
|
|
|
|
if ( details.mustInject && this.disabled === false ) {
|
2020-07-22 08:21:16 -06:00
|
|
|
vAPI.userStylesheet.add(`${selectorsStr}\n{${declarations}}`);
|
|
|
|
}
|
|
|
|
this.commit();
|
|
|
|
if ( details.silent !== true && this.hasListeners() ) {
|
|
|
|
this.triggerListeners({
|
|
|
|
declarative: [ [ selectorsStr, declarations ] ]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exceptCSSRules(exceptions) {
|
|
|
|
if ( exceptions.length === 0 ) { return; }
|
|
|
|
this.exceptedCSSRules.push(...exceptions);
|
|
|
|
if ( this.hasListeners() ) {
|
|
|
|
this.triggerListeners({ exceptions });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addListener(listener) {
|
|
|
|
if ( this.listeners.indexOf(listener) !== -1 ) { return; }
|
|
|
|
this.listeners.push(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
removeListener(listener) {
|
|
|
|
const pos = this.listeners.indexOf(listener);
|
|
|
|
if ( pos === -1 ) { return; }
|
|
|
|
this.listeners.splice(pos, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
hasListeners() {
|
|
|
|
return this.listeners.length !== 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
triggerListeners(changes) {
|
|
|
|
for ( const listener of this.listeners ) {
|
|
|
|
listener.onFiltersetChanged(changes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
toggle(state, callback) {
|
|
|
|
if ( state === undefined ) { state = this.disabled; }
|
|
|
|
if ( state !== this.disabled ) { return; }
|
|
|
|
this.disabled = !state;
|
|
|
|
const userStylesheet = vAPI.userStylesheet;
|
|
|
|
for ( const entry of this.filterset ) {
|
|
|
|
const rule = `${entry.selectors}\n{${entry.declarations}}`;
|
|
|
|
if ( this.disabled ) {
|
|
|
|
userStylesheet.remove(rule);
|
|
|
|
} else {
|
|
|
|
userStylesheet.add(rule);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
userStylesheet.apply(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Here we will deal with:
|
|
|
|
// - Injecting low priority user styles;
|
|
|
|
// - Notifying listeners about changed filterset.
|
|
|
|
// https://www.reddit.com/r/uBlockOrigin/comments/9jj0y1/no_longer_blocking_ads/
|
|
|
|
// Ensure vAPI is still valid -- it can go away by the time we are
|
|
|
|
// called, since the port could be force-disconnected from the main
|
|
|
|
// process. Another approach would be to have vAPI.SafeAnimationFrame
|
|
|
|
// register a shutdown job: to evaluate. For now I will keep the fix
|
|
|
|
// trivial.
|
2019-05-16 11:44:49 -06:00
|
|
|
commitNow() {
|
2020-07-22 08:21:16 -06:00
|
|
|
this.commitTimer.clear();
|
|
|
|
if ( vAPI instanceof Object === false ) { return; }
|
2020-10-13 05:19:06 -06:00
|
|
|
vAPI.userStylesheet.apply();
|
2020-07-24 17:08:48 -06:00
|
|
|
if ( this.proceduralFilterer instanceof Object ) {
|
|
|
|
this.proceduralFilterer.commitNow();
|
|
|
|
}
|
2019-05-16 11:44:49 -06:00
|
|
|
}
|
2016-11-12 11:38:41 -07:00
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
commit(commitNow) {
|
|
|
|
if ( commitNow ) {
|
|
|
|
this.commitTimer.clear();
|
|
|
|
this.commitNow();
|
|
|
|
} else {
|
|
|
|
this.commitTimer.start();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-24 17:08:48 -06:00
|
|
|
proceduralFiltererInstance() {
|
|
|
|
if ( this.proceduralFilterer instanceof Object === false ) {
|
|
|
|
this.proceduralFilterer = new DOMProceduralFilterer(this);
|
|
|
|
}
|
|
|
|
return this.proceduralFilterer;
|
|
|
|
}
|
|
|
|
|
2020-09-24 08:53:18 -06:00
|
|
|
addProceduralSelectors(selectors) {
|
|
|
|
if ( Array.isArray(selectors) === false || selectors.length === 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const procedurals = [];
|
|
|
|
for ( const raw of selectors ) {
|
|
|
|
const o = JSON.parse(raw);
|
|
|
|
if (
|
|
|
|
o.action !== undefined &&
|
|
|
|
o.action[0] === ':style' &&
|
|
|
|
o.tasks === undefined
|
|
|
|
) {
|
2020-10-13 05:19:06 -06:00
|
|
|
this.addCSSRule(o.selector, o.action[1], { mustInject: true });
|
2020-09-24 08:53:18 -06:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( o.pseudo !== undefined ) {
|
2020-10-13 05:19:06 -06:00
|
|
|
this.addCSSRule(o.selector, vAPI.hideStyle, { mustInject: true });
|
2020-09-24 08:53:18 -06:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
procedurals.push(o);
|
|
|
|
}
|
|
|
|
if ( procedurals.length !== 0 ) {
|
|
|
|
this.proceduralFiltererInstance()
|
|
|
|
.addProceduralSelectors(procedurals);
|
|
|
|
}
|
2019-05-16 11:44:49 -06:00
|
|
|
}
|
2016-08-12 06:55:35 -06:00
|
|
|
|
2019-05-16 11:44:49 -06:00
|
|
|
createProceduralFilter(o) {
|
2020-07-24 17:08:48 -06:00
|
|
|
return this.proceduralFiltererInstance().createProceduralFilter(o);
|
2019-05-16 11:44:49 -06:00
|
|
|
}
|
2016-08-12 06:55:35 -06:00
|
|
|
|
2020-09-07 06:28:01 -06:00
|
|
|
getAllSelectors(bits = 0) {
|
|
|
|
const out = {
|
|
|
|
declarative: [],
|
|
|
|
exceptions: this.exceptedCSSRules,
|
|
|
|
};
|
|
|
|
const hasProcedural = this.proceduralFilterer instanceof Object;
|
|
|
|
const includePrivateSelectors = (bits & 0b01) !== 0;
|
|
|
|
const masterToken = hasProcedural
|
|
|
|
? `[${this.proceduralFilterer.masterToken}]`
|
|
|
|
: undefined;
|
|
|
|
for ( const entry of this.filterset ) {
|
|
|
|
const selectors = entry.selectors;
|
|
|
|
if (
|
|
|
|
includePrivateSelectors === false &&
|
|
|
|
masterToken !== undefined &&
|
|
|
|
selectors.startsWith(masterToken)
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
out.declarative.push([ selectors, entry.declarations ]);
|
|
|
|
}
|
|
|
|
const excludeProcedurals = (bits & 0b10) !== 0;
|
|
|
|
if ( excludeProcedurals !== true ) {
|
|
|
|
out.procedural = hasProcedural
|
|
|
|
? Array.from(this.proceduralFilterer.selectors.values())
|
|
|
|
: [];
|
|
|
|
}
|
2019-05-16 11:44:49 -06:00
|
|
|
return out;
|
|
|
|
}
|
2016-08-12 06:55:35 -06:00
|
|
|
|
2019-05-16 11:44:49 -06:00
|
|
|
getAllExceptionSelectors() {
|
|
|
|
return this.exceptions.join(',\n');
|
|
|
|
}
|
2016-08-12 06:55:35 -06:00
|
|
|
};
|
2020-07-22 08:21:16 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
|
2016-08-12 06:55:35 -06:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
// vAPI.domCollapser
|
|
|
|
|
|
|
|
{
|
2018-12-26 08:45:19 -07:00
|
|
|
const messaging = vAPI.messaging;
|
|
|
|
const toCollapse = new Map();
|
|
|
|
const src1stProps = {
|
2020-10-05 07:13:07 -06:00
|
|
|
audio: 'currentSrc',
|
2018-12-26 08:45:19 -07:00
|
|
|
embed: 'src',
|
|
|
|
iframe: 'src',
|
2020-10-05 07:13:07 -06:00
|
|
|
img: 'currentSrc',
|
|
|
|
object: 'data',
|
|
|
|
video: 'currentSrc',
|
2015-03-29 10:13:28 -06:00
|
|
|
};
|
2018-12-26 08:45:19 -07:00
|
|
|
const src2ndProps = {
|
2020-10-05 07:13:07 -06:00
|
|
|
audio: 'src',
|
|
|
|
img: 'src',
|
|
|
|
video: 'src',
|
2015-06-04 09:17:02 -06:00
|
|
|
};
|
2018-12-26 08:45:19 -07:00
|
|
|
const tagToTypeMap = {
|
2020-10-05 07:13:07 -06:00
|
|
|
audio: 'media',
|
2017-08-03 08:18:05 -06:00
|
|
|
embed: 'object',
|
|
|
|
iframe: 'sub_frame',
|
|
|
|
img: 'image',
|
2020-10-05 07:13:07 -06:00
|
|
|
object: 'object',
|
|
|
|
video: 'media',
|
2017-08-03 08:18:05 -06:00
|
|
|
};
|
2018-12-26 08:45:19 -07:00
|
|
|
let resquestIdGenerator = 1,
|
|
|
|
processTimer,
|
|
|
|
cachedBlockedSet,
|
|
|
|
cachedBlockedSetHash,
|
|
|
|
cachedBlockedSetTimer,
|
2018-12-27 08:17:08 -07:00
|
|
|
toProcess = [],
|
|
|
|
toFilter = [],
|
2018-12-26 08:45:19 -07:00
|
|
|
netSelectorCacheCount = 0;
|
|
|
|
|
|
|
|
const cachedBlockedSetClear = function() {
|
2017-08-03 08:18:05 -06:00
|
|
|
cachedBlockedSet =
|
|
|
|
cachedBlockedSetHash =
|
|
|
|
cachedBlockedSetTimer = undefined;
|
2015-03-29 10:13:28 -06:00
|
|
|
};
|
|
|
|
|
2020-10-05 07:13:07 -06:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/399
|
|
|
|
// https://github.com/gorhill/uBlock/issues/2848
|
|
|
|
// Use a user stylesheet to collapse placeholders.
|
|
|
|
const getCollapseToken = ( ) => {
|
|
|
|
if ( collapseToken === undefined ) {
|
|
|
|
collapseToken = vAPI.randomToken();
|
|
|
|
vAPI.userStylesheet.add(
|
2020-10-05 12:20:43 -06:00
|
|
|
`[${collapseToken}]\n{display:none!important;}`,
|
2020-10-05 07:13:07 -06:00
|
|
|
true
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return collapseToken;
|
|
|
|
};
|
|
|
|
let collapseToken;
|
|
|
|
|
2017-08-03 08:18:05 -06:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/174
|
|
|
|
// Do not remove fragment from src URL
|
2018-12-26 08:45:19 -07:00
|
|
|
const onProcessed = function(response) {
|
2019-09-17 13:15:01 -06:00
|
|
|
// This happens if uBO is disabled or restarted.
|
|
|
|
if ( response instanceof Object === false ) {
|
2017-08-03 08:18:05 -06:00
|
|
|
toCollapse.clear();
|
2016-03-21 08:33:40 -06:00
|
|
|
return;
|
|
|
|
}
|
2017-08-03 08:18:05 -06:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const targets = toCollapse.get(response.id);
|
2017-08-03 08:18:05 -06:00
|
|
|
if ( targets === undefined ) { return; }
|
2020-10-05 07:13:07 -06:00
|
|
|
|
2017-08-03 08:18:05 -06:00
|
|
|
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 10:13:28 -06:00
|
|
|
}
|
2017-08-16 12:10:41 -06:00
|
|
|
if ( cachedBlockedSet === undefined || cachedBlockedSet.size === 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
2020-10-05 07:13:07 -06:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const selectors = [];
|
|
|
|
let netSelectorCacheCountMax = response.netSelectorCacheCountMax;
|
2017-08-03 08:18:05 -06:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
for ( const target of targets ) {
|
|
|
|
const tag = target.localName;
|
|
|
|
let prop = src1stProps[tag];
|
2017-08-03 08:18:05 -06:00
|
|
|
if ( prop === undefined ) { continue; }
|
2018-12-26 08:45:19 -07:00
|
|
|
let src = target[prop];
|
2017-08-03 08:18:05 -06:00
|
|
|
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 10:13:28 -06:00
|
|
|
}
|
2017-08-03 08:18:05 -06:00
|
|
|
if ( cachedBlockedSet.has(tagToTypeMap[tag] + ' ' + src) === false ) {
|
2015-03-29 10:13:28 -06:00
|
|
|
continue;
|
|
|
|
}
|
2020-10-05 07:13:07 -06:00
|
|
|
target.setAttribute(getCollapseToken(), '');
|
2017-08-03 08:18:05 -06:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/1048
|
2020-10-05 07:13:07 -06:00
|
|
|
// Use attribute to construct CSS rule
|
|
|
|
if ( netSelectorCacheCount > netSelectorCacheCountMax ) { continue; }
|
|
|
|
const value = target.getAttribute(prop);
|
|
|
|
if ( value ) {
|
|
|
|
selectors.push(`${tag}[${prop}="${CSS.escape(value)}"]`);
|
|
|
|
netSelectorCacheCount += 1;
|
2015-03-29 10:13:28 -06:00
|
|
|
}
|
|
|
|
}
|
2017-10-22 06:59:29 -06:00
|
|
|
|
2020-10-05 07:13:07 -06:00
|
|
|
if ( selectors.length === 0 ) { return; }
|
|
|
|
messaging.send('contentscript', {
|
|
|
|
what: 'cosmeticFiltersInjected',
|
|
|
|
type: 'net',
|
|
|
|
hostname: window.location.hostname,
|
|
|
|
selectors,
|
|
|
|
});
|
2015-03-29 10:13:28 -06:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const send = function() {
|
2017-08-03 08:18:05 -06:00
|
|
|
processTimer = undefined;
|
|
|
|
toCollapse.set(resquestIdGenerator, toProcess);
|
2019-09-17 13:15:01 -06:00
|
|
|
messaging.send('contentscript', {
|
2017-08-03 08:18:05 -06:00
|
|
|
what: 'getCollapsibleBlockedRequests',
|
|
|
|
id: resquestIdGenerator,
|
2017-10-01 05:56:28 -06:00
|
|
|
frameURL: window.location.href,
|
2017-08-03 08:18:05 -06:00
|
|
|
resources: toFilter,
|
2019-09-17 13:15:01 -06:00
|
|
|
hash: cachedBlockedSetHash,
|
|
|
|
}).then(response => {
|
|
|
|
onProcessed(response);
|
|
|
|
});
|
2018-12-27 08:17:08 -07:00
|
|
|
toProcess = [];
|
|
|
|
toFilter = [];
|
2017-08-03 08:18:05 -06:00
|
|
|
resquestIdGenerator += 1;
|
2015-03-29 10:13:28 -06:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const process = function(delay) {
|
2017-08-03 08:18:05 -06:00
|
|
|
if ( toProcess.length === 0 ) { return; }
|
2015-03-29 10:13:28 -06:00
|
|
|
if ( delay === 0 ) {
|
2017-08-03 08:18:05 -06:00
|
|
|
if ( processTimer !== undefined ) {
|
|
|
|
clearTimeout(processTimer);
|
|
|
|
}
|
2015-03-29 10:13:28 -06:00
|
|
|
send();
|
2017-08-03 08:18:05 -06:00
|
|
|
} else if ( processTimer === undefined ) {
|
|
|
|
processTimer = vAPI.setTimeout(send, delay || 20);
|
2015-03-29 10:13:28 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const add = function(target) {
|
2017-08-03 08:18:05 -06:00
|
|
|
toProcess[toProcess.length] = target;
|
2015-03-29 10:13:28 -06:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const addMany = function(targets) {
|
|
|
|
for ( const target of targets ) {
|
|
|
|
add(target);
|
2016-02-03 17:15:28 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const iframeSourceModified = function(mutations) {
|
|
|
|
for ( const mutation of mutations ) {
|
|
|
|
addIFrame(mutation.target, true);
|
2015-05-01 17:06:52 -06:00
|
|
|
}
|
|
|
|
process();
|
|
|
|
};
|
2018-12-26 08:45:19 -07:00
|
|
|
const iframeSourceObserver = new MutationObserver(iframeSourceModified);
|
|
|
|
const iframeSourceObserverOptions = {
|
2015-05-01 17:06:52 -06:00
|
|
|
attributes: true,
|
|
|
|
attributeFilter: [ 'src' ]
|
|
|
|
};
|
|
|
|
|
2018-05-20 04:49:12 -06:00
|
|
|
// https://github.com/gorhill/uBlock/issues/162
|
2020-07-19 07:06:19 -06:00
|
|
|
// Be prepared to deal with possible change of src attribute.
|
2018-12-26 08:45:19 -07:00
|
|
|
const addIFrame = function(iframe, dontObserve) {
|
2015-05-01 17:06:52 -06:00
|
|
|
if ( dontObserve !== true ) {
|
|
|
|
iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
|
|
|
|
}
|
2018-12-26 08:45:19 -07:00
|
|
|
const src = iframe.src;
|
2020-07-19 07:06:19 -06:00
|
|
|
if ( typeof src !== 'string' || src === '' ) { return; }
|
2018-05-20 04:49:12 -06:00
|
|
|
if ( src.startsWith('http') === false ) { return; }
|
2018-12-27 08:17:08 -07:00
|
|
|
toFilter.push({ type: 'sub_frame', url: iframe.src });
|
2017-08-03 08:18:05 -06:00
|
|
|
add(iframe);
|
2015-03-29 10:13:28 -06:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const addIFrames = function(iframes) {
|
|
|
|
for ( const iframe of iframes ) {
|
|
|
|
addIFrame(iframe);
|
2016-02-03 16:47:30 -07:00
|
|
|
}
|
2016-02-03 17:15:28 -07:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const onResourceFailed = function(ev) {
|
2017-08-03 08:18:05 -06:00
|
|
|
if ( tagToTypeMap[ev.target.localName] !== undefined ) {
|
2017-10-21 11:43:46 -06:00
|
|
|
add(ev.target);
|
|
|
|
process();
|
2017-08-03 08:18:05 -06:00
|
|
|
}
|
2016-08-13 14:42:58 -06:00
|
|
|
};
|
|
|
|
|
2020-07-24 17:08:48 -06:00
|
|
|
const stop = function() {
|
|
|
|
document.removeEventListener('error', onResourceFailed, true);
|
|
|
|
if ( processTimer !== undefined ) {
|
|
|
|
clearTimeout(processTimer);
|
|
|
|
}
|
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
|
|
|
vAPI.domWatcher.removeListener(domWatcherInterface);
|
|
|
|
}
|
|
|
|
vAPI.shutdown.remove(stop);
|
|
|
|
vAPI.domCollapser = null;
|
|
|
|
};
|
|
|
|
|
|
|
|
const start = function() {
|
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
|
|
|
vAPI.domWatcher.addListener(domWatcherInterface);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const domWatcherInterface = {
|
2017-10-21 11:43:46 -06:00
|
|
|
onDOMCreated: function() {
|
2019-09-30 05:50:35 -06:00
|
|
|
if ( self.vAPI instanceof Object === false ) { return; }
|
2017-10-21 11:43:46 -06:00
|
|
|
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
|
2018-12-26 08:45:19 -07:00
|
|
|
const elems = document.images ||
|
|
|
|
document.getElementsByTagName('img');
|
|
|
|
for ( const elem of elems ) {
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( elem.complete ) {
|
|
|
|
add(elem);
|
|
|
|
}
|
2016-08-12 06:55:35 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
addMany(document.embeds || document.getElementsByTagName('embed'));
|
|
|
|
addMany(document.getElementsByTagName('object'));
|
|
|
|
addIFrames(document.getElementsByTagName('iframe'));
|
|
|
|
process(0);
|
|
|
|
|
|
|
|
document.addEventListener('error', onResourceFailed, true);
|
|
|
|
|
2020-07-24 17:08:48 -06:00
|
|
|
vAPI.shutdown.add(stop);
|
2017-10-21 11:43:46 -06:00
|
|
|
},
|
|
|
|
onDOMChanged: function(addedNodes) {
|
2018-12-26 08:45:19 -07:00
|
|
|
if ( addedNodes.length === 0 ) { return; }
|
|
|
|
for ( const node of addedNodes ) {
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( node.localName === 'iframe' ) {
|
|
|
|
addIFrame(node);
|
|
|
|
}
|
|
|
|
if ( node.childElementCount === 0 ) { continue; }
|
2018-12-26 08:45:19 -07:00
|
|
|
const iframes = node.getElementsByTagName('iframe');
|
2016-08-12 06:55:35 -06:00
|
|
|
if ( iframes.length !== 0 ) {
|
|
|
|
addIFrames(iframes);
|
|
|
|
}
|
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
process();
|
2016-06-28 17:45:11 -06:00
|
|
|
}
|
2016-08-12 06:55:35 -06:00
|
|
|
};
|
|
|
|
|
2020-07-24 17:08:48 -06:00
|
|
|
vAPI.domCollapser = { start };
|
2020-07-22 08:21:16 -06:00
|
|
|
}
|
2015-03-29 10:13:28 -06:00
|
|
|
|
|
|
|
/******************************************************************************/
|
2016-06-27 17:09:04 -06:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
// vAPI.domSurveyor
|
|
|
|
|
|
|
|
{
|
2018-12-26 08:45:19 -07:00
|
|
|
const messaging = vAPI.messaging;
|
|
|
|
const queriedIds = new Set();
|
|
|
|
const queriedClasses = new Set();
|
2018-12-31 09:50:40 -07:00
|
|
|
const maxSurveyNodes = 65536;
|
|
|
|
const maxSurveyTimeSlice = 4;
|
|
|
|
const maxSurveyBuffer = 64;
|
2018-12-26 08:45:19 -07:00
|
|
|
|
|
|
|
let domFilterer,
|
2017-10-25 09:42:18 -06:00
|
|
|
hostname = '',
|
2016-10-06 08:49:46 -06:00
|
|
|
surveyCost = 0;
|
2014-06-23 16:42:43 -06:00
|
|
|
|
2018-12-31 09:50:40 -07:00
|
|
|
const pendingNodes = {
|
|
|
|
nodeLists: [],
|
|
|
|
buffer: [
|
|
|
|
null, null, null, null, null, null, null, null,
|
|
|
|
null, null, null, null, null, null, null, null,
|
|
|
|
null, null, null, null, null, null, null, null,
|
|
|
|
null, null, null, null, null, null, null, null,
|
|
|
|
null, null, null, null, null, null, null, null,
|
|
|
|
null, null, null, null, null, null, null, null,
|
|
|
|
null, null, null, null, null, null, null, null,
|
|
|
|
null, null, null, null, null, null, null, null,
|
|
|
|
],
|
|
|
|
j: 0,
|
|
|
|
accepted: 0,
|
|
|
|
iterated: 0,
|
|
|
|
stopped: false,
|
|
|
|
add: function(nodes) {
|
|
|
|
if ( nodes.length === 0 || this.accepted >= maxSurveyNodes ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.nodeLists.push(nodes);
|
|
|
|
this.accepted += nodes.length;
|
|
|
|
},
|
|
|
|
next: function() {
|
|
|
|
if ( this.nodeLists.length === 0 || this.stopped ) { return 0; }
|
|
|
|
const nodeLists = this.nodeLists;
|
|
|
|
let ib = 0;
|
|
|
|
do {
|
|
|
|
const nodeList = nodeLists[0];
|
|
|
|
let j = this.j;
|
|
|
|
let n = j + maxSurveyBuffer - ib;
|
|
|
|
if ( n > nodeList.length ) {
|
|
|
|
n = nodeList.length;
|
|
|
|
}
|
|
|
|
for ( let i = j; i < n; i++ ) {
|
|
|
|
this.buffer[ib++] = nodeList[j++];
|
|
|
|
}
|
|
|
|
if ( j !== nodeList.length ) {
|
|
|
|
this.j = j;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
this.j = 0;
|
|
|
|
this.nodeLists.shift();
|
|
|
|
} while ( ib < maxSurveyBuffer && nodeLists.length !== 0 );
|
|
|
|
this.iterated += ib;
|
|
|
|
if ( this.iterated >= maxSurveyNodes ) {
|
|
|
|
this.nodeLists = [];
|
|
|
|
this.stopped = true;
|
|
|
|
//console.info(`domSurveyor> Surveyed a total of ${this.iterated} nodes. Enough.`);
|
|
|
|
}
|
|
|
|
return ib;
|
|
|
|
},
|
|
|
|
hasNodes: function() {
|
|
|
|
return this.nodeLists.length !== 0;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
// Extract all classes/ids: these will be passed to the cosmetic
|
|
|
|
// filtering engine, and in return we will obtain only the relevant
|
|
|
|
// CSS selectors.
|
|
|
|
const reWhitespace = /\s/;
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
const surveyPhase1 = function() {
|
|
|
|
//console.time('dom surveyor/surveying');
|
|
|
|
const t0 = performance.now();
|
|
|
|
const rews = reWhitespace;
|
|
|
|
const ids = [];
|
|
|
|
const classes = [];
|
|
|
|
const nodes = pendingNodes.buffer;
|
|
|
|
const deadline = t0 + maxSurveyTimeSlice;
|
|
|
|
let qids = queriedIds;
|
|
|
|
let qcls = queriedClasses;
|
|
|
|
let processed = 0;
|
|
|
|
for (;;) {
|
|
|
|
const n = pendingNodes.next();
|
|
|
|
if ( n === 0 ) { break; }
|
|
|
|
for ( let i = 0; i < n; i++ ) {
|
|
|
|
const node = nodes[i]; nodes[i] = null;
|
|
|
|
let v = node.id;
|
|
|
|
if ( typeof v === 'string' && v.length !== 0 ) {
|
|
|
|
v = v.trim();
|
|
|
|
if ( qids.has(v) === false && v.length !== 0 ) {
|
|
|
|
ids.push(v); qids.add(v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let vv = node.className;
|
|
|
|
if ( typeof vv === 'string' && vv.length !== 0 ) {
|
|
|
|
if ( rews.test(vv) === false ) {
|
|
|
|
if ( qcls.has(vv) === false ) {
|
|
|
|
classes.push(vv); qcls.add(vv);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
vv = node.classList;
|
|
|
|
let j = vv.length;
|
|
|
|
while ( j-- ) {
|
|
|
|
const v = vv[j];
|
|
|
|
if ( qcls.has(v) === false ) {
|
|
|
|
classes.push(v); qcls.add(v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
processed += n;
|
|
|
|
if ( performance.now() >= deadline ) { break; }
|
|
|
|
}
|
|
|
|
const t1 = performance.now();
|
|
|
|
surveyCost += t1 - t0;
|
|
|
|
//console.info(`domSurveyor> Surveyed ${processed} nodes in ${(t1-t0).toFixed(2)} ms`);
|
|
|
|
// Phase 2: Ask main process to lookup relevant cosmetic filters.
|
|
|
|
if ( ids.length !== 0 || classes.length !== 0 ) {
|
2019-09-17 13:15:01 -06:00
|
|
|
messaging.send('contentscript', {
|
|
|
|
what: 'retrieveGenericCosmeticSelectors',
|
|
|
|
hostname,
|
|
|
|
ids,
|
|
|
|
classes,
|
|
|
|
exceptions: domFilterer.exceptions,
|
|
|
|
cost: surveyCost,
|
|
|
|
}).then(response => {
|
|
|
|
surveyPhase3(response);
|
|
|
|
});
|
2018-12-31 09:50:40 -07:00
|
|
|
} else {
|
|
|
|
surveyPhase3(null);
|
|
|
|
}
|
|
|
|
//console.timeEnd('dom surveyor/surveying');
|
|
|
|
};
|
|
|
|
|
|
|
|
const surveyTimer = new vAPI.SafeAnimationFrame(surveyPhase1);
|
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
// This is to shutdown the surveyor if result of surveying keeps being
|
2017-10-24 14:38:51 -06: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.
|
2018-12-26 08:45:19 -07:00
|
|
|
let canShutdownAfter = Date.now() + 300000,
|
2017-10-24 14:38:51 -06:00
|
|
|
surveyingMissCount = 0;
|
2017-10-21 11:43:46 -06:00
|
|
|
|
2016-08-12 06:55:35 -06:00
|
|
|
// Handle main process' response.
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const surveyPhase3 = function(response) {
|
|
|
|
const result = response && response.result;
|
|
|
|
let mustCommit = false;
|
2015-04-07 17:10:03 -06:00
|
|
|
|
2016-08-12 06:55:35 -06:00
|
|
|
if ( result ) {
|
2020-09-07 06:28:01 -06:00
|
|
|
let selectors = result.injected;
|
2017-10-26 04:18:03 -06:00
|
|
|
if ( typeof selectors === 'string' && selectors.length !== 0 ) {
|
2020-10-13 05:19:06 -06:00
|
|
|
domFilterer.addCSSRule(selectors, vAPI.hideStyle);
|
2017-10-26 04:18:03 -06:00
|
|
|
mustCommit = true;
|
|
|
|
}
|
2019-05-16 11:44:49 -06:00
|
|
|
selectors = result.excepted;
|
|
|
|
if ( Array.isArray(selectors) && selectors.length !== 0 ) {
|
|
|
|
domFilterer.exceptCSSRules(selectors);
|
|
|
|
}
|
2016-08-12 06:55:35 -06:00
|
|
|
}
|
2016-07-12 11:29:30 -06:00
|
|
|
|
2018-12-31 09:50:40 -07:00
|
|
|
if ( pendingNodes.stopped === false ) {
|
|
|
|
if ( pendingNodes.hasNodes() ) {
|
|
|
|
surveyTimer.start(1);
|
|
|
|
}
|
|
|
|
if ( mustCommit ) {
|
|
|
|
surveyingMissCount = 0;
|
|
|
|
canShutdownAfter = Date.now() + 300000;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
surveyingMissCount += 1;
|
|
|
|
if ( surveyingMissCount < 256 || Date.now() < canShutdownAfter ) {
|
|
|
|
return;
|
|
|
|
}
|
2017-10-24 14:38:51 -06:00
|
|
|
}
|
2014-07-04 14:47:34 -06:00
|
|
|
|
2017-12-13 06:02:55 -07:00
|
|
|
//console.info('dom surveyor shutting down: too many misses');
|
2014-09-16 17:16:18 -06:00
|
|
|
|
2017-10-24 14:38:51 -06:00
|
|
|
surveyTimer.clear();
|
2017-10-21 11:43:46 -06:00
|
|
|
vAPI.domWatcher.removeListener(domWatcherInterface);
|
|
|
|
vAPI.domSurveyor = null;
|
2016-08-12 06:55:35 -06:00
|
|
|
};
|
2014-07-04 14:47:34 -06:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const domWatcherInterface = {
|
2017-10-21 11:43:46 -06:00
|
|
|
onDOMCreated: function() {
|
|
|
|
if (
|
2019-09-30 05:50:35 -06:00
|
|
|
self.vAPI instanceof Object === false ||
|
2017-10-24 14:38:51 -06:00
|
|
|
vAPI.domSurveyor instanceof Object === false ||
|
2017-10-21 11:43:46 -06:00
|
|
|
vAPI.domFilterer instanceof Object === false
|
|
|
|
) {
|
2019-09-30 05:50:35 -06:00
|
|
|
if ( self.vAPI instanceof Object ) {
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
|
|
|
vAPI.domWatcher.removeListener(domWatcherInterface);
|
|
|
|
}
|
2017-10-24 14:38:51 -06:00
|
|
|
vAPI.domSurveyor = null;
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2017-10-23 10:21:37 -06:00
|
|
|
//console.time('dom surveyor/dom layout created');
|
2017-10-21 11:43:46 -06:00
|
|
|
domFilterer = vAPI.domFilterer;
|
2018-12-31 09:50:40 -07:00
|
|
|
pendingNodes.add(document.querySelectorAll('[id],[class]'));
|
2017-10-21 11:43:46 -06:00
|
|
|
surveyTimer.start();
|
2017-10-23 10:21:37 -06:00
|
|
|
//console.timeEnd('dom surveyor/dom layout created');
|
2017-10-21 11:43:46 -06:00
|
|
|
},
|
|
|
|
onDOMChanged: function(addedNodes) {
|
|
|
|
if ( addedNodes.length === 0 ) { return; }
|
2017-10-23 10:21:37 -06:00
|
|
|
//console.time('dom surveyor/dom layout changed');
|
2018-12-26 08:45:19 -07:00
|
|
|
let i = addedNodes.length;
|
2017-10-21 11:43:46 -06:00
|
|
|
while ( i-- ) {
|
2018-12-26 08:45:19 -07:00
|
|
|
const node = addedNodes[i];
|
2018-12-31 09:50:40 -07:00
|
|
|
pendingNodes.add([ node ]);
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( node.childElementCount === 0 ) { continue; }
|
2018-12-31 09:50:40 -07:00
|
|
|
pendingNodes.add(node.querySelectorAll('[id],[class]'));
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
2018-12-31 09:50:40 -07:00
|
|
|
if ( pendingNodes.hasNodes() ) {
|
2017-10-21 11:43:46 -06:00
|
|
|
surveyTimer.start(1);
|
|
|
|
}
|
2017-10-23 10:21:37 -06:00
|
|
|
//console.timeEnd('dom surveyor/dom layout changed');
|
2016-06-28 07:06:14 -06:00
|
|
|
}
|
2014-09-16 13:39:21 -06:00
|
|
|
};
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const start = function(details) {
|
2017-10-25 09:42:18 -06:00
|
|
|
if ( vAPI.domWatcher instanceof Object === false ) { return; }
|
|
|
|
hostname = details.hostname;
|
2017-10-21 11:43:46 -06:00
|
|
|
vAPI.domWatcher.addListener(domWatcherInterface);
|
2017-10-25 09:42:18 -06:00
|
|
|
};
|
2015-04-07 17:10:03 -06:00
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
vAPI.domSurveyor = { start };
|
|
|
|
}
|
2014-06-23 16:42:43 -06:00
|
|
|
|
2016-08-12 06:55:35 -06:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
2014-09-14 14:20:40 -06:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
// vAPI.bootstrap:
|
|
|
|
// Bootstrapping allows all components of the content script
|
|
|
|
// to be launched if/when needed.
|
2017-10-21 11:43:46 -06:00
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
{
|
2018-12-26 08:45:19 -07:00
|
|
|
const bootstrapPhase2 = function() {
|
2017-10-21 11:43:46 -06:00
|
|
|
// This can happen on Firefox. For instance:
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1893
|
|
|
|
if ( window.location === null ) { return; }
|
2019-09-30 05:50:35 -06:00
|
|
|
if ( self.vAPI instanceof Object === false ) { return; }
|
2017-10-25 09:42:18 -06:00
|
|
|
|
2019-09-17 13:15:01 -06:00
|
|
|
vAPI.messaging.send('contentscript', {
|
|
|
|
what: 'shouldRenderNoscriptTags',
|
|
|
|
});
|
2018-08-31 16:47:02 -06:00
|
|
|
|
2017-10-25 09:42:18 -06:00
|
|
|
if ( vAPI.domWatcher instanceof Object ) {
|
2017-10-21 11:43:46 -06:00
|
|
|
vAPI.domWatcher.start();
|
2016-08-13 14:42:58 -06:00
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
|
2017-10-25 09:42:18 -06:00
|
|
|
// Element picker works only in top window for now.
|
|
|
|
if (
|
|
|
|
window !== window.top ||
|
|
|
|
vAPI.domFilterer instanceof Object === false
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
2017-10-21 11:43:46 -06:00
|
|
|
|
2019-09-18 10:17:45 -06:00
|
|
|
// To be used by element picker/zapper.
|
|
|
|
vAPI.mouseClick = { x: -1, y: -1 };
|
2017-10-21 11:43:46 -06:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const onMouseClick = function(ev) {
|
2019-09-18 10:17:45 -06:00
|
|
|
if ( ev.isTrusted === false ) { return; }
|
|
|
|
vAPI.mouseClick.x = ev.clientX;
|
|
|
|
vAPI.mouseClick.y = ev.clientY;
|
|
|
|
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/1143
|
|
|
|
// Find a link under the mouse, to try to avoid confusing new tabs
|
|
|
|
// as nuisance popups.
|
2019-11-08 07:30:59 -07:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/777
|
|
|
|
// Mind that href may not be a string.
|
|
|
|
const elem = ev.target.closest('a[href]');
|
|
|
|
if ( elem === null || typeof elem.href !== 'string' ) { return; }
|
2019-09-17 13:15:01 -06:00
|
|
|
vAPI.messaging.send('contentscript', {
|
2019-09-18 10:17:45 -06:00
|
|
|
what: 'maybeGoodPopup',
|
|
|
|
url: elem.href || '',
|
2019-09-17 13:15:01 -06:00
|
|
|
});
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
2016-08-13 14:42:58 -06:00
|
|
|
|
2016-08-12 06:55:35 -06:00
|
|
|
document.addEventListener('mousedown', onMouseClick, true);
|
2015-01-01 19:14:53 -07:00
|
|
|
|
2016-08-12 06:55:35 -06:00
|
|
|
// https://github.com/gorhill/uMatrix/issues/144
|
|
|
|
vAPI.shutdown.add(function() {
|
|
|
|
document.removeEventListener('mousedown', onMouseClick, true);
|
|
|
|
});
|
2017-10-21 11:43:46 -06:00
|
|
|
};
|
|
|
|
|
2019-01-27 15:07:40 -07:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/403
|
|
|
|
// If there was a spurious port disconnection -- in which case the
|
|
|
|
// response is expressly set to `null`, rather than undefined or
|
|
|
|
// an object -- let's stay around, we may be given the opportunity
|
|
|
|
// to try bootstrapping again later.
|
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
const bootstrapPhase1 = function(response) {
|
2019-09-29 10:26:58 -06:00
|
|
|
if ( response instanceof Object === false ) { return; }
|
|
|
|
|
2019-01-27 15:07:40 -07:00
|
|
|
vAPI.bootstrap = undefined;
|
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
// cosmetic filtering engine aka 'cfe'
|
2018-12-26 08:45:19 -07:00
|
|
|
const cfeDetails = response && response.specificCosmeticFilters;
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( !cfeDetails || !cfeDetails.ready ) {
|
|
|
|
vAPI.domWatcher = vAPI.domCollapser = vAPI.domFilterer =
|
|
|
|
vAPI.domSurveyor = vAPI.domIsLoaded = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-24 17:08:48 -06:00
|
|
|
vAPI.domCollapser.start();
|
|
|
|
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( response.noCosmeticFiltering ) {
|
|
|
|
vAPI.domFilterer = null;
|
|
|
|
vAPI.domSurveyor = null;
|
|
|
|
} else {
|
2020-07-22 08:21:16 -06:00
|
|
|
const domFilterer = vAPI.domFilterer = new vAPI.DOMFilterer();
|
2017-10-21 11:43:46 -06:00
|
|
|
if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) {
|
|
|
|
vAPI.domSurveyor = null;
|
|
|
|
}
|
|
|
|
domFilterer.exceptions = cfeDetails.exceptionFilters;
|
2020-10-13 05:19:06 -06:00
|
|
|
domFilterer.addCSSRule(cfeDetails.injectedHideFilters, vAPI.hideStyle);
|
2017-10-21 11:43:46 -06:00
|
|
|
domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters);
|
2019-05-16 11:44:49 -06:00
|
|
|
domFilterer.exceptCSSRules(cfeDetails.exceptedFilters);
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
|
|
|
|
2017-10-22 06:59:29 -06:00
|
|
|
vAPI.userStylesheet.apply();
|
|
|
|
|
2017-10-23 07:01:00 -06:00
|
|
|
// Library of resources is located at:
|
|
|
|
// https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
|
2017-12-28 11:49:02 -07:00
|
|
|
if ( response.scriptlets ) {
|
|
|
|
vAPI.injectScriptlet(document, response.scriptlets);
|
|
|
|
vAPI.injectedScripts = response.scriptlets;
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
|
|
|
|
2017-10-25 09:42:18 -06:00
|
|
|
if ( vAPI.domSurveyor instanceof Object ) {
|
|
|
|
vAPI.domSurveyor.start(cfeDetails);
|
|
|
|
}
|
|
|
|
|
2017-10-21 11:43:46 -06: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 {
|
2018-12-26 08:45:19 -07:00
|
|
|
document.addEventListener(
|
|
|
|
'DOMContentLoaded',
|
|
|
|
bootstrapPhase2,
|
|
|
|
{ once: true }
|
|
|
|
);
|
2017-10-21 11:43:46 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-07-22 08:21:16 -06:00
|
|
|
vAPI.bootstrap = function() {
|
2019-09-17 13:15:01 -06:00
|
|
|
vAPI.messaging.send('contentscript', {
|
|
|
|
what: 'retrieveContentScriptParameters',
|
2020-07-29 05:38:49 -06:00
|
|
|
url: vAPI.effectiveSelf.location.href,
|
2019-09-17 13:15:01 -06:00
|
|
|
charset: document.characterSet,
|
|
|
|
}).then(response => {
|
|
|
|
bootstrapPhase1(response);
|
|
|
|
});
|
2019-01-27 15:07:40 -07:00
|
|
|
};
|
2020-07-22 08:21:16 -06:00
|
|
|
}
|
2015-01-01 19:14:53 -07:00
|
|
|
|
2019-01-27 15:07:40 -07:00
|
|
|
// This starts bootstrap process.
|
|
|
|
vAPI.bootstrap();
|
|
|
|
|
2015-01-01 19:14:53 -07:00
|
|
|
/******************************************************************************/
|
2016-06-27 17:09:04 -06:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
2017-08-17 06:25:02 -06:00
|
|
|
|
2018-12-26 08:45:19 -07:00
|
|
|
}
|
|
|
|
// <<<<<<<< end of HUGE-IF-BLOCK
|