2014-06-24 00:42:43 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2016-03-06 16:51:06 +01:00
|
|
|
uBlock Origin - a browser extension to block requests.
|
|
|
|
Copyright (C) 2014-2016 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';
|
|
|
|
|
2014-10-19 13:11:27 +02:00
|
|
|
/******************************************************************************/
|
2014-06-24 00:42:43 +02:00
|
|
|
|
|
|
|
// Injected into content pages
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Abort execution by throwing if an unexpected condition arise.
|
|
|
|
// - https://github.com/chrisaljoudi/uBlock/issues/456
|
|
|
|
|
|
|
|
if ( typeof vAPI !== 'object' || vAPI.contentscriptInjected ) {
|
|
|
|
throw new Error('Unexpected condition: aborting.');
|
|
|
|
}
|
|
|
|
|
|
|
|
vAPI.contentscriptInjected = true;
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2016-06-28 15:06:14 +02:00
|
|
|
// The DOM filterer is the heart of uBO's cosmetic filtering.
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
vAPI.domFilterer = {
|
|
|
|
allExceptions: Object.create(null),
|
|
|
|
allSelectors: Object.create(null),
|
|
|
|
cssNotHiddenId: '',
|
|
|
|
disabledId: String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 982451653 + 982451653).toString(36),
|
|
|
|
enabled: true,
|
|
|
|
hiddenId: String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 982451653 + 982451653).toString(36),
|
2016-06-28 15:06:14 +02:00
|
|
|
hiddenNodeCount: 0,
|
2016-06-28 01:09:04 +02:00
|
|
|
matchesProp: 'matches',
|
|
|
|
newSelectors: [],
|
|
|
|
shadowId: String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 982451653 + 982451653).toString(36),
|
|
|
|
styleTags: [],
|
|
|
|
xpathNotHiddenId: '',
|
|
|
|
|
|
|
|
complexGroupSelector: null,
|
|
|
|
complexSelectors: [],
|
2016-06-28 15:06:14 +02:00
|
|
|
complexSelectorsMissCount: 0,
|
2016-06-28 01:09:04 +02:00
|
|
|
simpleGroupSelector: null,
|
|
|
|
simpleSelectors: [],
|
|
|
|
|
|
|
|
complexHasSelectors: [],
|
2016-06-28 15:06:14 +02:00
|
|
|
complexHasSelectorsMissCount: 0,
|
2016-06-28 01:09:04 +02:00
|
|
|
simpleHasSelectors: [],
|
|
|
|
|
|
|
|
xpathExpression: null,
|
|
|
|
xpathResult: null,
|
2016-06-28 15:06:14 +02:00
|
|
|
xpathSelectors: [],
|
|
|
|
xpathSelectorsMissCount: 0,
|
2016-06-28 01:09:04 +02:00
|
|
|
|
|
|
|
addExceptions: function(aa) {
|
|
|
|
for ( var i = 0, n = aa.length; i < n; i++ ) {
|
|
|
|
this.allExceptions[aa[i]] = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addHasSelector: function(s1, s2) {
|
|
|
|
var entry = { a: s1, b: s2.slice(5, -1) };
|
|
|
|
if ( s1.indexOf(' ') === -1 ) {
|
|
|
|
this.simpleHasSelectors.push(entry);
|
|
|
|
} else {
|
|
|
|
this.complexHasSelectors.push(entry);
|
2016-06-28 15:06:14 +02:00
|
|
|
this.complexHasSelectorsMissCount = 0;
|
2016-06-28 01:09:04 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addSelector: function(s) {
|
|
|
|
if ( this.allSelectors[s] || this.allExceptions[s] ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.allSelectors[s] = true;
|
|
|
|
var pos = s.indexOf(':');
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
pos = s.indexOf(':has(');
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
this.addHasSelector(s.slice(0, pos), s.slice(pos));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( s.lastIndexOf(':xpath(', 0) === 0 ) {
|
|
|
|
this.addXpathSelector('', s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( s.indexOf(' ') === -1 ) {
|
|
|
|
this.simpleSelectors.push(s);
|
|
|
|
this.simpleGroupSelector = null;
|
|
|
|
} else {
|
|
|
|
this.complexSelectors.push(s);
|
|
|
|
this.complexGroupSelector = null;
|
2016-06-28 15:06:14 +02:00
|
|
|
this.complexSelectorsMissCount = 0;
|
2016-06-28 01:09:04 +02:00
|
|
|
}
|
|
|
|
this.newSelectors.push(s);
|
|
|
|
},
|
|
|
|
|
|
|
|
addSelectors: function(aa) {
|
|
|
|
for ( var i = 0, n = aa.length; i < n; i++ ) {
|
|
|
|
this.addSelector(aa[i]);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addXpathSelector: function(s1, s2) {
|
|
|
|
this.xpathSelectors.push(s2.slice(7, -1));
|
|
|
|
this.xpathExpression = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
checkStyleTags: function(commitIfNeeded) {
|
|
|
|
var doc = document,
|
|
|
|
html = doc.documentElement,
|
|
|
|
head = doc.head,
|
|
|
|
newParent = head || html;
|
|
|
|
if ( newParent === null ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var styles = this.styleTags,
|
|
|
|
style, oldParent,
|
|
|
|
mustCommit = false;
|
|
|
|
for ( var i = 0; i < styles.length; i++ ) {
|
|
|
|
style = styles[i];
|
|
|
|
oldParent = style.parentNode;
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1031
|
|
|
|
// If our style tag was disabled, re-insert into the page.
|
|
|
|
if (
|
|
|
|
style.disabled &&
|
|
|
|
oldParent !== null &&
|
|
|
|
style.hasAttribute(this.disabledId) === false
|
|
|
|
) {
|
|
|
|
oldParent.removeChild(style);
|
|
|
|
oldParent = null;
|
|
|
|
}
|
|
|
|
if ( oldParent === head || oldParent === html ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
style.disabled = false;
|
|
|
|
newParent.appendChild(style);
|
|
|
|
mustCommit = true;
|
|
|
|
}
|
|
|
|
if ( mustCommit && commitIfNeeded ) {
|
|
|
|
this.commit();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
commit: function(newNodes) {
|
2016-06-28 15:06:14 +02:00
|
|
|
var beforeHiddenNodeCount = this.hiddenNodeCount;
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( newNodes === undefined ) {
|
|
|
|
newNodes = [ document.documentElement ];
|
|
|
|
}
|
|
|
|
|
2016-06-28 15:06:14 +02:00
|
|
|
// Inject new selectors as CSS rules in a style tag.
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( this.newSelectors.length ) {
|
|
|
|
var styleTag = document.createElement('style');
|
|
|
|
styleTag.setAttribute('type', 'text/css');
|
|
|
|
styleTag.textContent =
|
|
|
|
':root ' +
|
|
|
|
this.newSelectors.join(',\n:root ') +
|
|
|
|
'\n{ display: none !important; }';
|
|
|
|
document.head.appendChild(styleTag);
|
|
|
|
this.styleTags.push(styleTag);
|
|
|
|
}
|
|
|
|
|
|
|
|
var nodes, node, parents, parent, i, j, k, entry;
|
|
|
|
|
|
|
|
// Simple `:has()` selectors.
|
|
|
|
i = this.simpleHasSelectors.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
entry = this.simpleHasSelectors[i];
|
|
|
|
parents = newNodes;
|
|
|
|
j = parents.length;
|
|
|
|
while ( j-- ) {
|
|
|
|
parent = parents[j];
|
|
|
|
if ( parent[this.matchesProp](entry.a) && parent.querySelector(entry.b) !== null ) {
|
|
|
|
this.hideNode(parent);
|
|
|
|
}
|
|
|
|
nodes = parent.querySelectorAll(entry.a + this.cssNotHiddenId);
|
|
|
|
k = nodes.length;
|
|
|
|
while ( k-- ) {
|
|
|
|
node = nodes[k];
|
|
|
|
if ( node.querySelector(entry.b) !== null ) {
|
|
|
|
this.hideNode(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Complex `:has()` selectors.
|
2016-06-28 15:06:14 +02:00
|
|
|
if ( this.complexHasSelectorsMissCount < 5 && this.complexHasSelectors.length ) {
|
|
|
|
this.complexHasSelectorsMissCount += 1;
|
|
|
|
i = this.complexHasSelectors.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
entry = this.complexHasSelectors[i];
|
|
|
|
nodes = document.querySelectorAll(entry.a + this.cssNotHiddenId);
|
|
|
|
j = nodes.length;
|
|
|
|
while ( j-- ) {
|
|
|
|
node = nodes[j];
|
|
|
|
if ( node.querySelector(entry.b) !== null ) {
|
|
|
|
this.hideNode(node);
|
|
|
|
this.complexHasSelectorsMissCount = 0;
|
|
|
|
}
|
2016-06-28 01:09:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// `:xpath()` selectors.
|
2016-06-28 15:06:14 +02:00
|
|
|
if ( this.xpathSelectorsMissCount < 5 && this.xpathSelectors.length ) {
|
|
|
|
this.xpathSelectorsMissCount += 1;
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( this.xpathExpression === null ) {
|
|
|
|
this.xpathExpression = document.createExpression(
|
|
|
|
this.xpathSelectors.join(this.xpathNotHiddenId + '|') + this.xpathNotHiddenId,
|
|
|
|
null
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.xpathResult = this.xpathExpression.evaluate(
|
|
|
|
document,
|
|
|
|
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
|
|
|
|
this.xpathResult
|
|
|
|
);
|
|
|
|
i = this.xpathResult.snapshotLength;
|
|
|
|
while ( i-- ) {
|
|
|
|
node = this.xpathResult.snapshotItem(i);
|
|
|
|
if ( node.nodeType === 1 ) {
|
|
|
|
this.hideNode(node);
|
2016-06-28 15:06:14 +02:00
|
|
|
this.xpathSelectorsMissCount = 0;
|
2016-06-28 01:09:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Simple selectors.
|
|
|
|
if ( this.simpleSelectors.length ) {
|
|
|
|
if ( this.simpleGroupSelector === null ) {
|
|
|
|
this.simpleGroupSelector =
|
|
|
|
this.simpleSelectors.join(this.cssNotHiddenId + ',') +
|
|
|
|
this.cssNotHiddenId;
|
|
|
|
}
|
|
|
|
parents = newNodes;
|
|
|
|
i = parents.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
parent = parents[i];
|
|
|
|
if ( parent[this.matchesProp](this.simpleGroupSelector) ) {
|
|
|
|
this.hideNode(parent);
|
|
|
|
}
|
|
|
|
nodes = parent.querySelectorAll(this.simpleGroupSelector);
|
|
|
|
j = nodes.length;
|
|
|
|
while ( j-- ) {
|
|
|
|
this.hideNode(nodes[j]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Complex selectors.
|
2016-06-28 15:06:14 +02:00
|
|
|
if ( this.complexSelectorsMissCount < 5 && this.complexSelectors.length ) {
|
|
|
|
this.complexSelectorsMissCount += 1;
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( this.complexGroupSelector === null ) {
|
|
|
|
this.complexGroupSelector =
|
|
|
|
this.complexSelectors.join(this.cssNotHiddenId + ',') +
|
|
|
|
this.cssNotHiddenId;
|
|
|
|
}
|
|
|
|
nodes = document.querySelectorAll(this.complexGroupSelector);
|
|
|
|
i = nodes.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
this.hideNode(nodes[i]);
|
2016-06-28 15:06:14 +02:00
|
|
|
this.complexSelectorsMissCount = 0;
|
2016-06-28 01:09:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset transient state.
|
|
|
|
this.newSelectors.length = 0;
|
2016-06-28 15:06:14 +02:00
|
|
|
|
|
|
|
// If DOM nodes have been affected, notify core process.
|
|
|
|
if ( this.hiddenNodeCount !== beforeHiddenNodeCount ) {
|
|
|
|
vAPI.messaging.send(
|
|
|
|
'contentscript',
|
|
|
|
{ what: 'cosmeticFiltersActivated' }
|
|
|
|
);
|
|
|
|
}
|
2016-06-28 01:09:04 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
hideNode: (function() {
|
|
|
|
if ( document.documentElement.shadowRoot === undefined ) {
|
|
|
|
return function(node) {
|
2016-06-28 15:06:14 +02:00
|
|
|
this.hiddenNodeCount += 1;
|
2016-06-28 01:09:04 +02:00
|
|
|
node.setAttribute(this.hiddenId, '');
|
|
|
|
if ( this.enabled ) {
|
|
|
|
node.style.setProperty('display', 'none', 'important');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return function(node) {
|
2016-06-28 15:06:14 +02:00
|
|
|
this.hiddenNodeCount += 1;
|
2016-06-28 01:09:04 +02:00
|
|
|
node.setAttribute(this.hiddenId, '');
|
|
|
|
var shadow = node.shadowRoot;
|
|
|
|
// https://www.chromestatus.com/features/4668884095336448
|
|
|
|
// "Multiple shadow roots is being deprecated."
|
|
|
|
if ( shadow !== null ) {
|
|
|
|
if ( shadow.className !== this.shadowId ) {
|
|
|
|
node.style.setProperty('display', 'none', 'important');
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// https://github.com/gorhill/uBlock/pull/555
|
|
|
|
// Not all nodes can be shadowed:
|
|
|
|
// https://github.com/w3c/webcomponents/issues/102
|
|
|
|
// https://github.com/gorhill/uBlock/issues/762
|
|
|
|
// Remove display style that might get in the way of the shadow
|
|
|
|
// node doing its magic.
|
|
|
|
try {
|
|
|
|
shadow = node.createShadowRoot();
|
|
|
|
shadow.className = this.shadowId;
|
|
|
|
node.style.removeProperty('display');
|
|
|
|
} catch (ex) {
|
|
|
|
node.style.setProperty('display', 'none', 'important');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})(),
|
|
|
|
|
|
|
|
toggleOff: function() {
|
|
|
|
this.enabled = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleOn: function() {
|
|
|
|
this.enabled = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Not everything could be initialized at declaration time.
|
2015-01-02 03:14:53 +01:00
|
|
|
(function() {
|
2016-06-28 01:09:04 +02:00
|
|
|
var df = vAPI.domFilterer;
|
|
|
|
df.cssNotHiddenId = ':not([' + df.hiddenId + '])';
|
|
|
|
df.xpathNotHiddenId = '[not(@' + df.hiddenId + ')]';
|
|
|
|
var docElem = document.documentElement;
|
|
|
|
if ( typeof docElem.matches !== 'function' ) {
|
|
|
|
if ( typeof docElem.mozMatchesSelector === 'function' ) {
|
|
|
|
df.matchesProp = 'mozMatchesSelector';
|
|
|
|
} else if ( typeof docElem.webkitMatchesSelector === 'function' ) {
|
|
|
|
df.matchesProp = 'webkitMatchesSelector';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})();
|
2015-01-02 03:14:53 +01:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// This is executed once, and since no hooks are left behind once the response
|
|
|
|
// is received, I expect this code to be garbage collected by the browser.
|
|
|
|
|
|
|
|
(function domIsLoading() {
|
2015-01-02 03:14:53 +01:00
|
|
|
|
2014-10-19 13:11:27 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
// Domain-based ABP cosmetic filters.
|
|
|
|
// These can be inserted before the DOM is loaded.
|
2015-01-13 21:52:15 +01:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var cosmeticFilters = function(details) {
|
|
|
|
var domFilterer = vAPI.domFilterer;
|
|
|
|
domFilterer.addExceptions(details.cosmeticDonthide);
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/143
|
|
|
|
domFilterer.addSelectors(details.cosmeticHide);
|
|
|
|
domFilterer.commit();
|
|
|
|
};
|
2015-01-13 21:52:15 +01:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
/******************************************************************************/
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var netFilters = function(details) {
|
|
|
|
var parent = document.head || document.documentElement;
|
|
|
|
if ( !parent ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var styleTag = document.createElement('style');
|
|
|
|
styleTag.setAttribute('type', 'text/css');
|
|
|
|
var text = details.netHide.join(',\n');
|
|
|
|
var css = details.netCollapse ?
|
|
|
|
'\n{display:none !important;}' :
|
|
|
|
'\n{visibility:hidden !important;}';
|
|
|
|
styleTag.appendChild(document.createTextNode(text + css));
|
|
|
|
parent.appendChild(styleTag);
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Create script tags and assign data URIs looked up from our library of
|
|
|
|
// redirection resources: Sometimes it is useful to use these resources as
|
|
|
|
// standalone scriptlets. These scriptlets are injected from within the
|
|
|
|
// content scripts because what must be injected, if anything, depends on the
|
|
|
|
// currently active filters, as selected by the user.
|
|
|
|
// Library of redirection resources is located at:
|
|
|
|
// https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
|
|
|
|
|
|
|
|
var injectScripts = function(scripts) {
|
|
|
|
var parent = document.head || document.documentElement;
|
|
|
|
if ( !parent ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var scriptTag = document.createElement('script');
|
|
|
|
// Have the injected script tag remove itself when execution completes: to
|
|
|
|
// keep DOM as clean as possible.
|
|
|
|
scripts +=
|
|
|
|
"\n" +
|
|
|
|
"(function() {\n" +
|
|
|
|
" var c = document.currentScript,\n" +
|
|
|
|
" p = c && c.parentNode;\n" +
|
|
|
|
" if ( p ) {\n" +
|
|
|
|
" p.removeChild(c);\n" +
|
|
|
|
" }\n" +
|
|
|
|
"})();";
|
|
|
|
scriptTag.appendChild(document.createTextNode(scripts));
|
|
|
|
parent.appendChild(scriptTag);
|
|
|
|
vAPI.injectedScripts = scripts;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var responseHandler = function(details) {
|
|
|
|
if ( details ) {
|
|
|
|
if (
|
|
|
|
(vAPI.skipCosmeticFiltering = details.skipCosmeticFiltering) !== true &&
|
|
|
|
(details.cosmeticHide.length !== 0 || details.cosmeticDonthide.length !== 0)
|
|
|
|
) {
|
|
|
|
cosmeticFilters(details);
|
|
|
|
}
|
|
|
|
if ( details.netHide.length !== 0 ) {
|
|
|
|
netFilters(details);
|
|
|
|
}
|
|
|
|
if ( details.scripts ) {
|
|
|
|
injectScripts(details.scripts);
|
|
|
|
}
|
|
|
|
// The port will never be used again at this point, disconnecting allows
|
|
|
|
// the browser to flush this script from memory.
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
vAPI.contentscriptInjected = details && details.ready;
|
|
|
|
};
|
2015-01-02 02:58:19 +01:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var url = window.location.href;
|
|
|
|
vAPI.messaging.send(
|
|
|
|
'contentscript',
|
|
|
|
{
|
|
|
|
what: 'retrieveDomainCosmeticSelectors',
|
|
|
|
pageURL: url,
|
|
|
|
locationURL: url
|
|
|
|
},
|
|
|
|
responseHandler
|
|
|
|
);
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
2015-04-08 01:10:03 +02:00
|
|
|
/******************************************************************************/
|
2014-06-24 00:42:43 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/7
|
2015-03-29 18:13:28 +02:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var domCollapser = (function() {
|
2015-03-29 18:13:28 +02:00
|
|
|
var timer = null;
|
|
|
|
var requestId = 1;
|
|
|
|
var newRequests = [];
|
2016-02-04 01:15:28 +01:00
|
|
|
var pendingRequests = Object.create(null);
|
2015-03-29 18:13:28 +02:00
|
|
|
var pendingRequestCount = 0;
|
2015-06-04 17:17:02 +02:00
|
|
|
var src1stProps = {
|
2015-03-29 18:13:28 +02:00
|
|
|
'embed': 'src',
|
|
|
|
'img': 'src',
|
|
|
|
'object': 'data'
|
|
|
|
};
|
2015-06-04 17:17:02 +02:00
|
|
|
var src2ndProps = {
|
|
|
|
'img': 'srcset'
|
|
|
|
};
|
2016-03-06 16:51:06 +01:00
|
|
|
var messaging = vAPI.messaging;
|
2015-03-29 18:13:28 +02:00
|
|
|
|
|
|
|
var PendingRequest = function(target, tagName, attr) {
|
|
|
|
this.id = requestId++;
|
|
|
|
this.target = target;
|
|
|
|
this.tagName = tagName;
|
|
|
|
this.attr = attr;
|
|
|
|
pendingRequests[this.id] = this;
|
|
|
|
pendingRequestCount += 1;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Because a while ago I have observed constructors are faster than
|
|
|
|
// literal object instanciations.
|
|
|
|
var BouncingRequest = function(id, tagName, url) {
|
|
|
|
this.id = id;
|
|
|
|
this.tagName = tagName;
|
|
|
|
this.url = url;
|
|
|
|
this.collapse = false;
|
|
|
|
};
|
|
|
|
|
2015-04-08 01:10:03 +02:00
|
|
|
var onProcessed = function(response) {
|
2016-03-21 15:33:40 +01:00
|
|
|
// This can happens if uBO is restarted.
|
|
|
|
if ( !response ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-08 01:10:03 +02:00
|
|
|
// https://github.com/gorhill/uMatrix/issues/144
|
|
|
|
if ( response.shutdown ) {
|
2015-04-08 01:34:22 +02:00
|
|
|
vAPI.shutdown.exec();
|
2015-04-08 01:10:03 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var requests = response.result;
|
2015-03-29 18:13:28 +02:00
|
|
|
if ( requests === null || Array.isArray(requests) === false ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var selectors = [];
|
|
|
|
var i = requests.length;
|
|
|
|
var request, entry, target, value;
|
|
|
|
while ( i-- ) {
|
|
|
|
request = requests[i];
|
2016-02-04 01:15:28 +01:00
|
|
|
entry = pendingRequests[request.id];
|
|
|
|
if ( entry === undefined ) {
|
2015-03-29 18:13:28 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
delete pendingRequests[request.id];
|
|
|
|
pendingRequestCount -= 1;
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/869
|
2015-03-29 18:13:28 +02:00
|
|
|
if ( !request.collapse ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
target = entry.target;
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/399
|
2015-03-29 18:13:28 +02:00
|
|
|
// Never remove elements from the DOM, just hide them
|
|
|
|
target.style.setProperty('display', 'none', 'important');
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/1048
|
2015-03-29 18:13:28 +02:00
|
|
|
// Use attribute to construct CSS rule
|
2015-08-18 17:44:24 +02:00
|
|
|
if ( (value = target.getAttribute(entry.attr)) ) {
|
2015-03-29 18:13:28 +02:00
|
|
|
selectors.push(entry.tagName + '[' + entry.attr + '="' + value + '"]');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
}
|
|
|
|
// Renew map: I believe that even if all properties are deleted, an
|
|
|
|
// object will still use more memory than a brand new one.
|
|
|
|
if ( pendingRequestCount === 0 ) {
|
2016-02-04 01:15:28 +01:00
|
|
|
pendingRequests = Object.create(null);
|
2015-03-29 18:13:28 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var send = function() {
|
|
|
|
timer = null;
|
2016-03-06 16:51:06 +01:00
|
|
|
messaging.send(
|
|
|
|
'contentscript',
|
|
|
|
{
|
|
|
|
what: 'filterRequests',
|
|
|
|
pageURL: window.location.href,
|
|
|
|
pageHostname: window.location.hostname,
|
|
|
|
requests: newRequests
|
|
|
|
}, onProcessed
|
|
|
|
);
|
2015-03-29 18:13:28 +02:00
|
|
|
newRequests = [];
|
|
|
|
};
|
|
|
|
|
|
|
|
var process = function(delay) {
|
|
|
|
if ( newRequests.length === 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( delay === 0 ) {
|
|
|
|
clearTimeout(timer);
|
|
|
|
send();
|
|
|
|
} else if ( timer === null ) {
|
2015-05-17 19:02:56 +02:00
|
|
|
timer = vAPI.setTimeout(send, delay || 20);
|
2015-03-29 18:13:28 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// If needed eventually, we could listen to `src` attribute changes
|
|
|
|
// for iframes.
|
|
|
|
|
|
|
|
var add = function(target) {
|
|
|
|
var tagName = target.localName;
|
2015-06-04 17:17:02 +02:00
|
|
|
var prop = src1stProps[tagName];
|
2015-03-29 18:13:28 +02:00
|
|
|
if ( prop === undefined ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/174
|
2015-03-29 18:13:28 +02:00
|
|
|
// Do not remove fragment from src URL
|
|
|
|
var src = target[prop];
|
2015-06-04 17:17:02 +02:00
|
|
|
if ( typeof src !== 'string' || src.length === 0 ) {
|
|
|
|
prop = src2ndProps[tagName];
|
|
|
|
if ( prop === undefined ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
src = target[prop];
|
|
|
|
if ( typeof src !== 'string' || src.length === 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
2015-03-29 18:13:28 +02:00
|
|
|
}
|
|
|
|
var req = new PendingRequest(target, tagName, prop);
|
|
|
|
newRequests.push(new BouncingRequest(req.id, tagName, src));
|
|
|
|
};
|
|
|
|
|
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 ) {
|
|
|
|
var scriptTag = document.createElement('script');
|
|
|
|
scriptTag.appendChild(document.createTextNode(vAPI.injectedScripts));
|
|
|
|
var parent = iframe.contentDocument && iframe.contentDocument.head;
|
|
|
|
if ( parent ) {
|
|
|
|
parent.appendChild(scriptTag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
if ( src.lastIndexOf('http', 0) !== 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var req = new PendingRequest(iframe, 'iframe', 'src');
|
|
|
|
newRequests.push(new BouncingRequest(req.id, 'iframe', src));
|
|
|
|
};
|
|
|
|
|
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
|
|
|
};
|
|
|
|
|
|
|
|
var iframesFromNode = function(node) {
|
|
|
|
if ( node.localName === 'iframe' ) {
|
|
|
|
addIFrame(node);
|
|
|
|
}
|
|
|
|
addIFrames(node.getElementsByTagName('iframe'));
|
2015-03-29 18:13:28 +02:00
|
|
|
process();
|
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
2015-03-29 18:13:28 +02:00
|
|
|
iframesFromNode: iframesFromNode,
|
|
|
|
process: process
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
2016-06-28 01:09:04 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var domIsLoaded = function(ev) {
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
if ( ev ) {
|
|
|
|
document.removeEventListener('DOMContentLoaded', domIsLoaded);
|
|
|
|
}
|
|
|
|
|
|
|
|
// I've seen this happens on Firefox
|
|
|
|
if ( window.location === null ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/587
|
|
|
|
// Pointless to execute without the start script having done its job.
|
|
|
|
if ( !vAPI.contentscriptInjected ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-02-14 18:16:36 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// Cosmetic filters
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2014-07-30 14:05:00 +02:00
|
|
|
(function() {
|
2014-12-19 20:00:46 +01:00
|
|
|
if ( vAPI.skipCosmeticFiltering ) {
|
2015-04-08 01:10:03 +02:00
|
|
|
//console.debug('Abort cosmetic filtering');
|
2014-12-19 20:00:46 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-31 05:55:10 +01:00
|
|
|
//console.debug('Start cosmetic filtering');
|
2015-04-08 01:10:03 +02:00
|
|
|
|
2015-03-02 02:26:33 +01:00
|
|
|
//var timer = window.performance || Date;
|
|
|
|
//var tStart = timer.now();
|
2015-03-02 01:25:36 +01:00
|
|
|
|
2015-10-31 05:55:10 +01:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/789
|
|
|
|
// https://github.com/gorhill/uBlock/issues/873
|
|
|
|
// Be sure that our style tags used for cosmetic filtering are still applied.
|
2016-06-28 01:09:04 +02:00
|
|
|
var domFilterer = vAPI.domFilterer;
|
|
|
|
domFilterer.checkStyleTags(false);
|
|
|
|
domFilterer.commit();
|
2015-10-31 05:55:10 +01:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var contextNodes = [ document.documentElement ];
|
2016-03-06 16:51:06 +01:00
|
|
|
var messaging = vAPI.messaging;
|
2014-08-12 18:19:54 +02:00
|
|
|
var highGenerics = null;
|
2016-06-28 01:09:04 +02:00
|
|
|
var highHighGenericsInjected = false;
|
|
|
|
var lowGenericSelectors = [];
|
|
|
|
var queriedSelectors = Object.create(null);
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var responseHandler = function(response) {
|
2015-04-08 01:10:03 +02:00
|
|
|
// https://github.com/gorhill/uMatrix/issues/144
|
|
|
|
if ( response && response.shutdown ) {
|
2015-04-08 01:34:22 +02:00
|
|
|
vAPI.shutdown.exec();
|
2015-04-08 01:10:03 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-03-02 02:26:33 +01:00
|
|
|
//var tStart = timer.now();
|
2015-04-08 01:10:03 +02:00
|
|
|
var result = response && response.result;
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( result ) {
|
|
|
|
if ( result.hide.length ) {
|
|
|
|
processLowGenerics(result.hide);
|
|
|
|
}
|
|
|
|
if ( result.highGenerics ) {
|
|
|
|
highGenerics = result.highGenerics;
|
|
|
|
}
|
2014-08-12 18:19:54 +02:00
|
|
|
}
|
|
|
|
if ( highGenerics ) {
|
|
|
|
if ( highGenerics.hideLowCount ) {
|
2016-06-28 01:09:04 +02:00
|
|
|
processHighLowGenerics(highGenerics.hideLow);
|
2014-08-12 18:19:54 +02:00
|
|
|
}
|
|
|
|
if ( highGenerics.hideMediumCount ) {
|
2016-06-28 01:09:04 +02:00
|
|
|
processHighMediumGenerics(highGenerics.hideMedium);
|
2014-08-12 18:19:54 +02:00
|
|
|
}
|
|
|
|
if ( highGenerics.hideHighCount ) {
|
2014-09-17 01:16:18 +02:00
|
|
|
processHighHighGenericsAsync();
|
2014-08-12 18:19:54 +02:00
|
|
|
}
|
|
|
|
}
|
2016-06-28 01:09:04 +02:00
|
|
|
domFilterer.commit(contextNodes);
|
|
|
|
contextNodes = [];
|
2015-03-02 02:26:33 +01:00
|
|
|
//console.debug('%f: uBlock: CSS injection time', timer.now() - tStart);
|
2014-07-02 18:02:29 +02:00
|
|
|
};
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var retrieveGenericSelectors = function() {
|
|
|
|
if ( lowGenericSelectors.length !== 0 || highGenerics === null ) {
|
|
|
|
messaging.send(
|
|
|
|
'contentscript',
|
|
|
|
{
|
|
|
|
what: 'retrieveGenericCosmeticSelectors',
|
|
|
|
pageURL: window.location.href,
|
|
|
|
selectors: lowGenericSelectors,
|
|
|
|
firstSurvey: highGenerics === null
|
|
|
|
},
|
|
|
|
responseHandler
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
responseHandler(null);
|
|
|
|
}
|
|
|
|
lowGenericSelectors = [];
|
|
|
|
};
|
2015-01-01 14:09:44 +01:00
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
// Ensure elements matching a set of selectors are visually removed
|
2014-09-17 01:16:18 +02:00
|
|
|
// from the page, by:
|
|
|
|
// - Modifying the style property on the elements themselves
|
|
|
|
// - Injecting a style tag
|
|
|
|
// Extract and return the staged nodes which (may) match the selectors.
|
|
|
|
|
2014-08-12 18:19:54 +02:00
|
|
|
var selectNodes = function(selector) {
|
|
|
|
var targetNodes = [];
|
|
|
|
var i = contextNodes.length;
|
|
|
|
var node, nodeList, j;
|
|
|
|
var doc = document;
|
|
|
|
while ( i-- ) {
|
|
|
|
node = contextNodes[i];
|
|
|
|
if ( node === doc ) {
|
|
|
|
return doc.querySelectorAll(selector);
|
|
|
|
}
|
|
|
|
targetNodes.push(node);
|
|
|
|
nodeList = node.querySelectorAll(selector);
|
|
|
|
j = nodeList.length;
|
|
|
|
while ( j-- ) {
|
|
|
|
targetNodes.push(nodeList[j]);
|
|
|
|
}
|
2014-07-04 22:47:34 +02:00
|
|
|
}
|
2014-08-12 18:19:54 +02:00
|
|
|
return targetNodes;
|
2014-07-04 22:47:34 +02:00
|
|
|
};
|
|
|
|
|
2014-09-17 01:16:18 +02:00
|
|
|
// Low generics:
|
|
|
|
// - [id]
|
|
|
|
// - [class]
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var processLowGenerics = function(generics) {
|
|
|
|
domFilterer.addSelectors(generics);
|
2014-07-04 22:47:34 +02:00
|
|
|
};
|
|
|
|
|
2014-09-17 01:16:18 +02:00
|
|
|
// High-low generics:
|
|
|
|
// - [alt="..."]
|
|
|
|
// - [title="..."]
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var processHighLowGenerics = function(generics) {
|
2014-08-12 18:19:54 +02:00
|
|
|
var attrs = ['title', 'alt'];
|
2014-08-14 02:03:55 +02:00
|
|
|
var attr, attrValue, nodeList, iNode, node;
|
|
|
|
var selector;
|
2015-08-18 17:44:24 +02:00
|
|
|
while ( (attr = attrs.pop()) ) {
|
2014-08-12 18:19:54 +02:00
|
|
|
nodeList = selectNodes('[' + attr + ']');
|
|
|
|
iNode = nodeList.length;
|
|
|
|
while ( iNode-- ) {
|
|
|
|
node = nodeList[iNode];
|
|
|
|
attrValue = node.getAttribute(attr);
|
|
|
|
if ( !attrValue ) { continue; }
|
2015-01-02 01:09:40 +01:00
|
|
|
// Candidate 1 = generic form
|
2015-01-02 01:16:02 +01:00
|
|
|
// If generic form is injected, no need to process the specific
|
2015-01-02 01:09:40 +01:00
|
|
|
// form, as the generic will affect all related specific forms
|
2014-08-12 18:19:54 +02:00
|
|
|
selector = '[' + attr + '="' + attrValue + '"]';
|
2015-01-02 01:09:40 +01:00
|
|
|
if ( generics.hasOwnProperty(selector) ) {
|
2016-06-28 01:09:04 +02:00
|
|
|
domFilterer.addSelector(selector);
|
|
|
|
domFilterer.hideNode(node);
|
|
|
|
continue;
|
2014-08-12 18:19:54 +02:00
|
|
|
}
|
2015-01-02 01:09:40 +01:00
|
|
|
// Candidate 2 = specific form
|
2015-03-29 18:13:28 +02:00
|
|
|
selector = node.localName + selector;
|
2015-01-02 01:09:40 +01:00
|
|
|
if ( generics.hasOwnProperty(selector) ) {
|
2016-06-28 01:09:04 +02:00
|
|
|
domFilterer.addSelector(selector);
|
|
|
|
domFilterer.hideNode(node);
|
2014-08-12 18:19:54 +02:00
|
|
|
}
|
|
|
|
}
|
2014-07-04 22:47:34 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-09-17 01:16:18 +02:00
|
|
|
// High-medium generics:
|
|
|
|
// - [href^="http"]
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var processHighMediumGenerics = function(generics) {
|
2015-11-01 17:25:36 +01:00
|
|
|
var doc = document;
|
|
|
|
var i = contextNodes.length;
|
|
|
|
var aa = [ null ];
|
|
|
|
var node, nodes;
|
|
|
|
while ( i-- ) {
|
|
|
|
node = contextNodes[i];
|
|
|
|
if ( node.localName === 'a' ) {
|
|
|
|
aa[0] = node;
|
2016-06-28 01:09:04 +02:00
|
|
|
processHighMediumGenericsForNodes(aa, generics);
|
2015-11-01 17:25:36 +01:00
|
|
|
}
|
|
|
|
nodes = node.getElementsByTagName('a');
|
|
|
|
if ( nodes.length === 0 ) { continue; }
|
2016-06-28 01:09:04 +02:00
|
|
|
processHighMediumGenericsForNodes(nodes, generics);
|
2015-11-01 17:25:36 +01:00
|
|
|
if ( node === doc ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2015-09-11 23:59:25 +02:00
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
var processHighMediumGenericsForNodes = function(nodes, generics) {
|
2015-11-01 17:25:36 +01:00
|
|
|
var i = nodes.length;
|
|
|
|
var node, href, pos, hash, selectors, j, selector;
|
|
|
|
var aa = [ '' ];
|
|
|
|
while ( i-- ) {
|
|
|
|
node = nodes[i];
|
2014-08-12 18:19:54 +02:00
|
|
|
href = node.getAttribute('href');
|
|
|
|
if ( !href ) { continue; }
|
|
|
|
pos = href.indexOf('://');
|
|
|
|
if ( pos === -1 ) { continue; }
|
|
|
|
hash = href.slice(pos + 3, pos + 11);
|
2014-08-14 02:03:55 +02:00
|
|
|
selectors = generics[hash];
|
|
|
|
if ( selectors === undefined ) { continue; }
|
2015-09-11 23:59:25 +02:00
|
|
|
// A string.
|
|
|
|
if ( typeof selectors === 'string' ) {
|
2015-11-01 17:25:36 +01:00
|
|
|
aa[0] = selectors;
|
|
|
|
selectors = aa;
|
2015-09-11 23:59:25 +02:00
|
|
|
}
|
|
|
|
// An array of strings.
|
2015-11-01 17:25:36 +01:00
|
|
|
j = selectors.length;
|
|
|
|
while ( j-- ) {
|
|
|
|
selector = selectors[j];
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( href.lastIndexOf(selector, 8) === 8 ) {
|
|
|
|
domFilterer.addSelector(selector);
|
|
|
|
domFilterer.hideNode(node);
|
2014-08-14 02:03:55 +02:00
|
|
|
}
|
2014-07-02 18:02:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
// High-high generics are very costly to process, so we will coalesce
|
2014-09-17 01:16:18 +02:00
|
|
|
// requests to process high-high generics into as few requests as possible.
|
2016-06-28 01:09:04 +02:00
|
|
|
// The gain is significant on bloated pages.
|
2014-09-17 01:16:18 +02:00
|
|
|
|
2015-03-02 16:54:15 +01:00
|
|
|
var processHighHighGenericsMisses = 8;
|
2014-09-17 01:16:18 +02:00
|
|
|
var processHighHighGenericsTimer = null;
|
|
|
|
|
|
|
|
var processHighHighGenerics = function() {
|
|
|
|
processHighHighGenericsTimer = null;
|
2015-03-13 17:26:54 +01:00
|
|
|
if ( highGenerics.hideHigh === '' ) {
|
|
|
|
return;
|
|
|
|
}
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( highHighGenericsInjected ) {
|
2015-03-02 01:25:36 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-10-31 05:55:10 +01:00
|
|
|
// When there are too many misses for these highly generic CSS rules,
|
|
|
|
// we will just give up on looking whether they need to be injected.
|
2015-03-02 01:54:17 +01:00
|
|
|
if ( document.querySelector(highGenerics.hideHigh) === null ) {
|
2015-03-02 16:54:15 +01:00
|
|
|
processHighHighGenericsMisses -= 1;
|
|
|
|
if ( processHighHighGenericsMisses === 0 ) {
|
2016-06-28 01:09:04 +02:00
|
|
|
highHighGenericsInjected = true;
|
2015-03-02 02:26:33 +01:00
|
|
|
}
|
2015-03-02 01:25:36 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-06-28 01:09:04 +02:00
|
|
|
highHighGenericsInjected = true;
|
2014-10-17 21:44:19 +02:00
|
|
|
// We need to filter out possible exception cosmetic filters from
|
2014-09-17 01:16:18 +02:00
|
|
|
// high-high generics selectors.
|
2016-06-28 01:09:04 +02:00
|
|
|
domFilterer.addSelectors(highGenerics.hideHigh.split(',\n'));
|
|
|
|
domFilterer.commit();
|
2014-09-17 01:16:18 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
var processHighHighGenericsAsync = function() {
|
|
|
|
if ( processHighHighGenericsTimer !== null ) {
|
|
|
|
clearTimeout(processHighHighGenericsTimer);
|
|
|
|
}
|
2015-05-17 19:02:56 +02:00
|
|
|
processHighHighGenericsTimer = vAPI.setTimeout(processHighHighGenerics, 300);
|
2014-07-02 18:02:29 +02:00
|
|
|
};
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
// Extract all classes/ids: these will be passed to the cosmetic filtering
|
2014-09-17 01:16:18 +02:00
|
|
|
// engine, and in return we will obtain only the relevant CSS selectors.
|
|
|
|
|
2015-09-04 22:30:53 +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-28 01:09:04 +02:00
|
|
|
var classesAndIdsFromNodeList = function(nodes) {
|
2014-07-20 21:00:26 +02:00
|
|
|
if ( !nodes || !nodes.length ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var qq = queriedSelectors;
|
2015-03-02 01:25:36 +01:00
|
|
|
var ll = lowGenericSelectors;
|
2016-06-28 01:09:04 +02:00
|
|
|
var node, v, vv, len, c, beg, end;
|
2014-07-20 21:00:26 +02:00
|
|
|
var i = nodes.length;
|
|
|
|
while ( i-- ) {
|
2016-06-28 01:09:04 +02:00
|
|
|
node = nodes[i];
|
|
|
|
if ( node.nodeType !== 1 ) { continue; }
|
|
|
|
v = node.id;
|
|
|
|
if ( v !== '' && typeof v === 'string' ) {
|
|
|
|
v = '#' + v.trim();
|
|
|
|
if ( v !== '#' && qq[v] === undefined ) {
|
|
|
|
ll.push(v);
|
|
|
|
qq[v] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
vv = node.className;
|
|
|
|
if ( vv === '' || typeof vv !== 'string' ) { continue; }
|
2015-09-04 22:30:53 +02:00
|
|
|
len = vv.length;
|
|
|
|
beg = 0;
|
|
|
|
for (;;) {
|
|
|
|
// Skip whitespaces
|
|
|
|
while ( beg !== len ) {
|
|
|
|
c = vv.charCodeAt(beg);
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( c > 0x20 ) { break; }
|
2015-09-04 22:30:53 +02:00
|
|
|
beg++;
|
|
|
|
}
|
|
|
|
if ( beg === len ) { break; }
|
|
|
|
end = beg + 1;
|
|
|
|
// Skip non-whitespaces
|
|
|
|
while ( end !== len ) {
|
|
|
|
c = vv.charCodeAt(end);
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( c <= 0x20 ) { break; }
|
2015-09-04 22:30:53 +02:00
|
|
|
end++;
|
|
|
|
}
|
|
|
|
v = '.' + vv.slice(beg, end);
|
2016-06-28 01:09:04 +02:00
|
|
|
if ( qq[v] === undefined ) {
|
2015-09-04 22:30:53 +02:00
|
|
|
ll.push(v);
|
|
|
|
qq[v] = true;
|
|
|
|
}
|
|
|
|
if ( end === len ) { break; }
|
|
|
|
beg = end + 1;
|
2014-07-02 18:02:29 +02:00
|
|
|
}
|
2014-06-24 00:42:43 +02:00
|
|
|
}
|
2014-07-02 18:02:29 +02:00
|
|
|
};
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2014-09-17 01:16:18 +02:00
|
|
|
// Start cosmetic filtering.
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
classesAndIdsFromNodeList(document.querySelectorAll('[class],[id]'));
|
2015-02-14 18:16:36 +01:00
|
|
|
retrieveGenericSelectors();
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2015-03-02 02:26:33 +01:00
|
|
|
//console.debug('%f: uBlock: survey time', timer.now() - tStart);
|
2015-03-02 01:25:36 +01:00
|
|
|
|
2014-09-17 01:16:18 +02:00
|
|
|
// Below this point is the code which takes care to observe changes in
|
|
|
|
// the page and to add if needed relevant CSS rules as a result of the
|
|
|
|
// changes.
|
|
|
|
|
2014-07-30 14:05:00 +02:00
|
|
|
// Observe changes in the DOM only if...
|
|
|
|
// - there is a document.body
|
|
|
|
// - there is at least one `script` tag
|
|
|
|
if ( !document.body || !document.querySelector('script') ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/618
|
2015-03-29 18:13:28 +02:00
|
|
|
// Following is to observe dynamically added iframes:
|
|
|
|
// - On Firefox, the iframes fails to fire a `load` event
|
|
|
|
|
2014-08-12 18:19:54 +02:00
|
|
|
var ignoreTags = {
|
2014-08-14 19:59:37 +02:00
|
|
|
'link': true,
|
2014-08-12 18:19:54 +02:00
|
|
|
'script': true,
|
2015-03-29 18:13:28 +02:00
|
|
|
'style': true
|
2014-08-12 18:19:54 +02:00
|
|
|
};
|
|
|
|
|
2014-09-16 21:39:21 +02:00
|
|
|
// Added node lists will be cumulated here before being processed
|
|
|
|
var addedNodeLists = [];
|
|
|
|
var addedNodeListsTimer = null;
|
2016-06-28 15:06:14 +02:00
|
|
|
var addedNodeListsTimerDelay = 25;
|
2015-10-31 05:55:10 +01:00
|
|
|
var removedNodeListsTimer = null;
|
2016-06-28 15:06:14 +02:00
|
|
|
var removedNodeListsTimerDelay = 25;
|
2016-06-28 01:09:04 +02:00
|
|
|
var collapser = domCollapser;
|
2014-09-16 21:39:21 +02:00
|
|
|
|
2015-10-31 05:55:10 +01:00
|
|
|
var addedNodesHandler = function() {
|
|
|
|
addedNodeListsTimer = null;
|
2016-06-28 15:06:14 +02:00
|
|
|
if ( addedNodeListsTimerDelay < 100 ) {
|
|
|
|
addedNodeListsTimerDelay *= 2;
|
|
|
|
}
|
2015-12-19 23:58:26 +01:00
|
|
|
var iNodeList = addedNodeLists.length,
|
|
|
|
nodeList, iNode, node;
|
|
|
|
while ( iNodeList-- ) {
|
|
|
|
nodeList = addedNodeLists[iNodeList];
|
2014-08-12 18:19:54 +02:00
|
|
|
iNode = nodeList.length;
|
|
|
|
while ( iNode-- ) {
|
|
|
|
node = nodeList[iNode];
|
2014-09-24 23:41:05 +02:00
|
|
|
if ( node.nodeType !== 1 ) {
|
2014-08-12 18:19:54 +02:00
|
|
|
continue;
|
|
|
|
}
|
2015-03-29 18:13:28 +02:00
|
|
|
if ( ignoreTags.hasOwnProperty(node.localName) ) {
|
2014-08-12 18:19:54 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
contextNodes.push(node);
|
2015-03-29 18:13:28 +02:00
|
|
|
collapser.iframesFromNode(node);
|
2014-07-30 14:05:00 +02:00
|
|
|
}
|
|
|
|
}
|
2015-12-19 23:58:26 +01:00
|
|
|
addedNodeLists.length = 0;
|
2014-08-12 18:19:54 +02:00
|
|
|
if ( contextNodes.length !== 0 ) {
|
2016-06-28 01:09:04 +02:00
|
|
|
classesAndIdsFromNodeList(selectNodes('[class],[id]'));
|
2014-08-12 18:19:54 +02:00
|
|
|
retrieveGenericSelectors();
|
2014-07-30 14:05:00 +02:00
|
|
|
}
|
2014-07-02 18:02:29 +02:00
|
|
|
};
|
2014-08-12 18:19:54 +02:00
|
|
|
|
2015-10-31 05:55:10 +01:00
|
|
|
// https://github.com/gorhill/uBlock/issues/873
|
|
|
|
// This will ensure our style elements will stay in the DOM.
|
|
|
|
var removedNodesHandler = function() {
|
|
|
|
removedNodeListsTimer = null;
|
2016-06-28 15:06:14 +02:00
|
|
|
removedNodeListsTimerDelay *= 2;
|
|
|
|
// Stop watching style tags after a while.
|
|
|
|
if ( removedNodeListsTimerDelay > 1000 ) {
|
|
|
|
removedNodeListsTimerDelay = 0;
|
|
|
|
}
|
2016-06-28 01:09:04 +02:00
|
|
|
domFilterer.checkStyleTags(true);
|
2015-10-31 05:55:10 +01:00
|
|
|
};
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/205
|
2014-09-16 21:39:21 +02:00
|
|
|
// Do not handle added node directly from within mutation observer.
|
2015-10-31 05:55:10 +01:00
|
|
|
// I arbitrarily chose 100 ms for now: I have to compromise between the
|
|
|
|
// overhead of processing too few nodes too often and the delay of many
|
|
|
|
// nodes less often.
|
|
|
|
var domLayoutChanged = function(mutations) {
|
|
|
|
var removedNodeLists = false;
|
2014-09-16 21:39:21 +02:00
|
|
|
var iMutation = mutations.length;
|
2015-10-31 05:55:10 +01:00
|
|
|
var nodeList, mutation;
|
2014-09-16 21:39:21 +02:00
|
|
|
while ( iMutation-- ) {
|
2015-10-31 05:55:10 +01:00
|
|
|
mutation = mutations[iMutation];
|
|
|
|
nodeList = mutation.addedNodes;
|
2014-09-24 23:41:05 +02:00
|
|
|
if ( nodeList.length !== 0 ) {
|
2014-09-16 21:39:21 +02:00
|
|
|
addedNodeLists.push(nodeList);
|
|
|
|
}
|
2015-10-31 05:55:10 +01:00
|
|
|
if ( mutation.removedNodes.length !== 0 ) {
|
|
|
|
removedNodeLists = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( addedNodeLists.length !== 0 && addedNodeListsTimer === null ) {
|
2016-06-28 01:09:04 +02:00
|
|
|
addedNodeListsTimer = vAPI.setTimeout(addedNodesHandler, addedNodeListsTimerDelay);
|
2014-09-16 21:39:21 +02:00
|
|
|
}
|
2016-06-28 15:06:14 +02:00
|
|
|
if ( removedNodeListsTimerDelay !== 0 && removedNodeLists && removedNodeListsTimer === null ) {
|
|
|
|
removedNodeListsTimer = vAPI.setTimeout(removedNodesHandler, removedNodeListsTimerDelay);
|
2014-09-16 21:39:21 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-04-08 01:10:03 +02:00
|
|
|
//console.debug('Starts cosmetic filtering\'s mutations observer');
|
|
|
|
|
2014-07-30 14:05:00 +02:00
|
|
|
// https://github.com/gorhill/httpswitchboard/issues/176
|
2015-10-31 05:55:10 +01:00
|
|
|
var domLayoutObserver = new MutationObserver(domLayoutChanged);
|
|
|
|
domLayoutObserver.observe(document.body, {
|
2014-07-30 14:05:00 +02:00
|
|
|
childList: true,
|
|
|
|
subtree: true
|
|
|
|
});
|
2015-04-08 01:10:03 +02:00
|
|
|
|
|
|
|
// https://github.com/gorhill/uMatrix/issues/144
|
2015-04-08 01:34:22 +02:00
|
|
|
vAPI.shutdown.add(function() {
|
2015-10-31 05:55:10 +01:00
|
|
|
domLayoutObserver.disconnect();
|
2015-04-08 01:10:03 +02:00
|
|
|
if ( addedNodeListsTimer !== null ) {
|
|
|
|
clearTimeout(addedNodeListsTimer);
|
|
|
|
}
|
2016-03-06 16:51:06 +01:00
|
|
|
if ( removedNodeListsTimer !== null ) {
|
|
|
|
clearTimeout(removedNodeListsTimer);
|
|
|
|
}
|
|
|
|
if ( processHighHighGenericsTimer !== null ) {
|
|
|
|
clearTimeout(processHighHighGenericsTimer);
|
|
|
|
}
|
2015-04-08 01:10:03 +02:00
|
|
|
});
|
2014-07-02 18:02:29 +02:00
|
|
|
})();
|
2014-06-24 00:42:43 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-09-14 22:20:40 +02:00
|
|
|
// Permanent
|
2014-06-27 23:06:42 +02:00
|
|
|
|
2015-03-29 18:13:28 +02:00
|
|
|
// Listener to collapse blocked resources.
|
|
|
|
// - Future requests not blocked yet
|
|
|
|
// - Elements dynamically added to the page
|
|
|
|
// - Elements which resource URL changes
|
2015-02-04 06:24:12 +01:00
|
|
|
|
2015-04-08 01:10:03 +02:00
|
|
|
(function() {
|
|
|
|
var onResourceFailed = function(ev) {
|
|
|
|
//console.debug('onResourceFailed(%o)', ev);
|
2016-06-28 01:09:04 +02:00
|
|
|
domCollapser.add(ev.target);
|
|
|
|
domCollapser.process();
|
2015-04-08 01:10:03 +02:00
|
|
|
};
|
|
|
|
document.addEventListener('error', onResourceFailed, true);
|
|
|
|
|
|
|
|
// https://github.com/gorhill/uMatrix/issues/144
|
2015-04-08 01:34:22 +02:00
|
|
|
vAPI.shutdown.add(function() {
|
2015-04-08 01:10:03 +02:00
|
|
|
document.removeEventListener('error', onResourceFailed, true);
|
|
|
|
});
|
|
|
|
})();
|
2014-06-24 00:42:43 +02:00
|
|
|
|
2014-09-14 22:20:40 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/7
|
2015-11-01 17:25:36 +01:00
|
|
|
// Executed only once.
|
|
|
|
// Preferring getElementsByTagName over querySelectorAll:
|
|
|
|
// http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
|
2014-09-14 22:20:40 +02:00
|
|
|
|
|
|
|
(function() {
|
2016-06-28 01:09:04 +02:00
|
|
|
var collapser = domCollapser;
|
2016-02-04 01:15:28 +01:00
|
|
|
var elems = document.getElementsByTagName('img'),
|
|
|
|
i = elems.length, elem;
|
2015-03-29 18:13:28 +02:00
|
|
|
while ( i-- ) {
|
|
|
|
elem = elems[i];
|
|
|
|
if ( elem.complete ) {
|
|
|
|
collapser.add(elem);
|
2015-02-25 02:10:10 +01:00
|
|
|
}
|
2015-03-29 18:13:28 +02:00
|
|
|
}
|
2016-02-04 01:15:28 +01:00
|
|
|
collapser.addMany(document.getElementsByTagName('embed'));
|
|
|
|
collapser.addMany(document.getElementsByTagName('object'));
|
|
|
|
collapser.addIFrames(document.getElementsByTagName('iframe'));
|
2015-03-29 18:13:28 +02:00
|
|
|
collapser.process(0);
|
2014-09-14 22:20:40 +02:00
|
|
|
})();
|
|
|
|
|
2014-09-28 20:38:17 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-12-12 22:13:00 +01:00
|
|
|
// To send mouse coordinates to main process, as the chrome API fails
|
2014-09-28 20:38:17 +02:00
|
|
|
// to provide the mouse position to context menu listeners.
|
2015-12-12 22:13:00 +01:00
|
|
|
|
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/1143
|
|
|
|
// Also, find a link under the mouse, to try to avoid confusing new tabs
|
|
|
|
// as nuisance popups.
|
2014-09-28 20:38:17 +02:00
|
|
|
|
|
|
|
// Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
|
|
|
|
|
|
|
|
(function() {
|
2015-02-16 18:17:26 +01:00
|
|
|
if ( window !== window.top ) {
|
|
|
|
return;
|
|
|
|
}
|
2016-03-06 16:51:06 +01:00
|
|
|
|
|
|
|
var messaging = vAPI.messaging;
|
|
|
|
|
2015-09-10 19:46:18 +02:00
|
|
|
var onMouseClick = function(ev) {
|
2015-10-13 20:04:48 +02:00
|
|
|
var elem = ev.target;
|
|
|
|
while ( elem !== null && elem.localName !== 'a' ) {
|
|
|
|
elem = elem.parentElement;
|
|
|
|
}
|
2016-03-06 16:51:06 +01:00
|
|
|
messaging.send(
|
|
|
|
'contentscript',
|
|
|
|
{
|
|
|
|
what: 'mouseClick',
|
|
|
|
x: ev.clientX,
|
|
|
|
y: ev.clientY,
|
|
|
|
url: elem !== null ? elem.href : ''
|
|
|
|
});
|
2014-09-28 20:38:17 +02:00
|
|
|
};
|
2014-10-17 21:44:19 +02:00
|
|
|
|
2016-03-06 16:51:06 +01:00
|
|
|
document.addEventListener('mousedown', onMouseClick, true);
|
2015-04-08 01:10:03 +02:00
|
|
|
|
|
|
|
// https://github.com/gorhill/uMatrix/issues/144
|
2015-04-08 01:34:22 +02:00
|
|
|
vAPI.shutdown.add(function() {
|
2015-09-10 20:01:16 +02:00
|
|
|
document.removeEventListener('mousedown', onMouseClick, true);
|
2015-04-08 01:10:03 +02:00
|
|
|
});
|
2014-09-28 20:38:17 +02:00
|
|
|
})();
|
|
|
|
|
2015-01-02 03:14:53 +01:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2016-06-28 01:09:04 +02:00
|
|
|
};
|
2015-01-02 03:14:53 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
2016-06-28 01:09:04 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
if ( document.readyState !== 'loading' ) {
|
|
|
|
domIsLoaded();
|
|
|
|
} else {
|
|
|
|
document.addEventListener('DOMContentLoaded', domIsLoaded);
|
|
|
|
}
|