uBlock/src/js/contentscript-end.js

946 lines
32 KiB
JavaScript
Raw Normal View History

2014-06-23 16:42:43 -06:00
/*******************************************************************************
2015-03-07 11:20:18 -07:00
µBlock - a browser extension to block requests.
2014-06-23 16:42:43 -06:00
Copyright (C) 2014 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, HTMLDocument, XMLDocument */
2014-11-16 12:06:29 -07:00
/******************************************************************************/
2014-06-23 16:42:43 -06:00
// Injected into content pages
2015-01-01 19:14:53 -07:00
(function() {
'use strict';
/******************************************************************************/
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/464
2015-01-13 13:52:15 -07:00
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
2015-05-08 16:28:01 -06:00
}
// I've seen this happens on Firefox
if ( window.location === null ) {
return;
2015-01-13 13:52:15 -07:00
}
// This can happen
2015-05-16 11:40:56 -06:00
if ( typeof vAPI !== 'object' ) {
2015-01-23 09:32:49 -07:00
//console.debug('contentscript-end.js > vAPI not found');
2015-01-13 13:52:15 -07:00
return;
}
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/587
2015-01-23 09:32:49 -07:00
// Pointless to execute without the start script having done its job.
if ( !vAPI.contentscriptStartInjected ) {
2015-01-13 13:52:15 -07:00
return;
}
2014-06-23 16:42:43 -06:00
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/456
// Already injected?
2015-01-01 19:14:53 -07:00
if ( vAPI.contentscriptEndInjected ) {
2015-01-23 09:32:49 -07:00
//console.debug('contentscript-end.js > content script already injected');
return;
}
2015-01-01 19:14:53 -07:00
vAPI.contentscriptEndInjected = true;
vAPI.styles = vAPI.styles || [];
/******************************************************************************/
var messager = vAPI.messaging.channel('contentscript-end.js');
2014-06-23 16:42:43 -06:00
// https://github.com/gorhill/uMatrix/issues/144
2015-04-07 17:34:22 -06:00
vAPI.shutdown.add(function() {
messager.close();
});
/******************************************************************************/
2014-06-23 16:42:43 -06:00
/******************************************************************************/
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/789
2015-02-14 10:16:36 -07:00
// Be sure that specific cosmetic filters are still applied.
// Executed once, then flushed from memory.
(function() {
// Were there specific cosmetic filters?
if ( typeof vAPI.specificHideStyle !== 'object' ) {
2015-02-14 10:16:36 -07:00
return;
}
// Is our style tag still in the DOM? (the guess is whatever parent there
// is, it is in the DOM)
if ( vAPI.specificHideStyle.parentNode !== null ) {
2015-02-14 10:16:36 -07:00
return;
}
// Put it back
var parent = document.head || document.documentElement;
if ( parent ) {
parent.appendChild(vAPI.specificHideStyle);
2015-02-14 10:16:36 -07:00
}
})();
2015-03-29 10:13:28 -06:00
/******************************************************************************/
/******************************************************************************/
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/7
2015-03-29 10:13:28 -06:00
var uBlockCollapser = (function() {
var timer = null;
var requestId = 1;
var newRequests = [];
var pendingRequests = {};
var pendingRequestCount = 0;
2015-06-04 09:17:02 -06:00
var src1stProps = {
2015-03-29 10:13:28 -06:00
'embed': 'src',
'iframe': 'src',
'img': 'src',
'object': 'data'
};
2015-06-04 09:17:02 -06:00
var src2ndProps = {
'img': 'srcset'
};
2015-03-29 10:13:28 -06: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;
};
var onProcessed = function(response) {
// https://github.com/gorhill/uMatrix/issues/144
if ( response.shutdown ) {
2015-04-07 17:34:22 -06:00
vAPI.shutdown.exec();
return;
}
var requests = response.result;
2015-03-29 10:13:28 -06: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];
if ( pendingRequests.hasOwnProperty(request.id) === false ) {
continue;
}
entry = pendingRequests[request.id];
delete pendingRequests[request.id];
pendingRequestCount -= 1;
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/869
2015-03-29 10:13:28 -06:00
if ( !request.collapse ) {
continue;
}
target = entry.target;
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/399
2015-03-29 10:13:28 -06:00
// Never remove elements from the DOM, just hide them
target.style.setProperty('display', 'none', 'important');
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/1048
2015-03-29 10:13:28 -06:00
// Use attribute to construct CSS rule
if ( (value = target.getAttribute(entry.attr)) ) {
2015-03-29 10:13:28 -06:00
selectors.push(entry.tagName + '[' + entry.attr + '="' + value + '"]');
}
}
if ( selectors.length !== 0 ) {
messager.send({
what: 'cosmeticFiltersInjected',
2015-03-29 10:13:28 -06:00
type: 'net',
hostname: window.location.hostname,
selectors: selectors
});
}
// 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 ) {
pendingRequests = {};
}
};
var send = function() {
timer = null;
messager.send({
what: 'filterRequests',
pageURL: window.location.href,
pageHostname: window.location.hostname,
requests: newRequests
}, onProcessed);
newRequests = [];
};
var process = function(delay) {
if ( newRequests.length === 0 ) {
return;
}
if ( delay === 0 ) {
clearTimeout(timer);
send();
} else if ( timer === null ) {
timer = vAPI.setTimeout(send, delay || 20);
2015-03-29 10:13:28 -06:00
}
};
// If needed eventually, we could listen to `src` attribute changes
// for iframes.
var add = function(target) {
var tagName = target.localName;
2015-06-04 09:17:02 -06:00
var prop = src1stProps[tagName];
2015-03-29 10:13:28 -06:00
if ( prop === undefined ) {
return;
}
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/174
2015-03-29 10:13:28 -06:00
// Do not remove fragment from src URL
var src = target[prop];
2015-06-04 09:17:02 -06: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 10:13:28 -06:00
}
if ( src.lastIndexOf('http', 0) !== 0 ) {
return;
}
var req = new PendingRequest(target, tagName, prop);
newRequests.push(new BouncingRequest(req.id, tagName, src));
};
2015-05-01 17:06:52 -06: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' ]
};
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 10:13:28 -06:00
var src = iframe.src;
if ( src === '' || typeof src !== 'string' ) {
return;
}
if ( src.lastIndexOf('http', 0) !== 0 ) {
return;
}
var req = new PendingRequest(iframe, 'iframe', 'src');
newRequests.push(new BouncingRequest(req.id, 'iframe', src));
};
var iframesFromNode = function(node) {
if ( node.localName === 'iframe' ) {
2015-03-29 10:16:34 -06:00
addIFrame(node);
2015-03-29 10:13:28 -06:00
}
var iframes = node.querySelectorAll('iframe');
var i = iframes.length;
while ( i-- ) {
addIFrame(iframes[i]);
}
process();
};
return {
add: add,
addIFrame: addIFrame,
iframesFromNode: iframesFromNode,
process: process
};
})();
/******************************************************************************/
2015-02-14 10:16:36 -07:00
/******************************************************************************/
// Cosmetic filters
2014-06-23 16:42:43 -06:00
(function() {
2014-12-19 12:00:46 -07:00
if ( vAPI.skipCosmeticFiltering ) {
//console.debug('Abort cosmetic filtering');
2014-12-19 12:00:46 -07:00
return;
}
//console.debug('Starts cosmetic filtering');
2015-03-01 18:26:33 -07:00
//var timer = window.performance || Date;
//var tStart = timer.now();
2014-07-02 10:02:29 -06:00
var queriedSelectors = {};
2014-12-31 21:09:22 -07:00
var injectedSelectors = {};
var lowGenericSelectors = [];
var highGenerics = null;
var contextNodes = [document];
var nullArray = { push: function(){} };
2014-07-02 10:02:29 -06:00
var retrieveGenericSelectors = function() {
2015-03-01 17:43:42 -07:00
if ( lowGenericSelectors.length !== 0 || highGenerics === null ) {
//console.log('µBlock> ABP cosmetic filters: retrieving CSS rules using %d selectors', lowGenericSelectors.length);
messager.send({
what: 'retrieveGenericCosmeticSelectors',
pageURL: window.location.href,
selectors: lowGenericSelectors,
firstSurvey: highGenerics === null
},
retrieveHandler
);
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/452
retrieveHandler = nextRetrieveHandler;
} else {
nextRetrieveHandler(null);
}
lowGenericSelectors = [];
2014-12-31 21:09:22 -07:00
};
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/452
// This needs to be executed *after* the response from our query is
2014-12-31 21:09:22 -07:00
// received, not at `DOMContentLoaded` time, or else there is a good
// likeliness to outrun contentscript-start.js, which may still be waiting
// on a response from its own query.
var firstRetrieveHandler = function(response) {
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/158
2014-11-16 12:06:29 -07:00
// Ensure injected styles are enforced
// rhill 2014-11-16: not sure this is needed anymore. Test case in
// above issue was fine without the line below..
2014-11-27 04:41:28 -07:00
var selectors = vAPI.hideCosmeticFilters;
if ( typeof selectors === 'object' ) {
2014-12-31 21:09:22 -07:00
injectedSelectors = selectors;
2015-03-28 09:09:36 -06:00
hideElements(Object.keys(selectors));
2014-06-25 16:44:35 -06:00
}
// Add exception filters into injected filters collection, in order
// to force them to be seen as "already injected".
2014-11-27 04:41:28 -07:00
selectors = vAPI.donthideCosmeticFilters;
if ( typeof selectors === 'object' ) {
2015-01-01 05:46:33 -07:00
for ( var selector in selectors ) {
2014-11-27 04:41:28 -07:00
if ( selectors.hasOwnProperty(selector) ) {
injectedSelectors[selector] = true;
}
}
}
// Flush dead code from memory
firstRetrieveHandler = null;
// These are sent only once
var result = response && response.result;
if ( result ) {
if ( result.highGenerics ) {
highGenerics = result.highGenerics;
}
if ( result.donthide ) {
processLowGenerics(result.donthide, nullArray);
}
}
nextRetrieveHandler(response);
2014-07-02 10:02:29 -06:00
};
2014-06-23 16:42:43 -06:00
var nextRetrieveHandler = function(response) {
// https://github.com/gorhill/uMatrix/issues/144
if ( response && response.shutdown ) {
2015-04-07 17:34:22 -06:00
vAPI.shutdown.exec();
return;
}
2015-03-01 18:26:33 -07:00
//var tStart = timer.now();
//console.debug('µBlock> contextNodes = %o', contextNodes);
var result = response && response.result;
var hideSelectors = [];
if ( result && result.hide.length ) {
processLowGenerics(result.hide, hideSelectors);
}
if ( highGenerics ) {
if ( highGenerics.hideLowCount ) {
processHighLowGenerics(highGenerics.hideLow, hideSelectors);
}
if ( highGenerics.hideMediumCount ) {
processHighMediumGenerics(highGenerics.hideMedium, hideSelectors);
}
if ( highGenerics.hideHighCount ) {
processHighHighGenericsAsync();
}
}
if ( hideSelectors.length !== 0 ) {
addStyleTag(hideSelectors);
2014-06-23 16:42:43 -06:00
}
contextNodes.length = 0;
2015-03-01 18:26:33 -07:00
//console.debug('%f: uBlock: CSS injection time', timer.now() - tStart);
2014-07-02 10:02:29 -06:00
};
2014-06-23 16:42:43 -06:00
var retrieveHandler = firstRetrieveHandler;
// Ensure elements matching a set of selectors are visually removed
// from the page, by:
// - Modifying the style property on the elements themselves
// - Injecting a style tag
var addStyleTag = function(selectors) {
var selectorStr = selectors.join(',\n');
var style = document.createElement('style');
// The linefeed before the style block is very important: do no remove!
2015-03-28 09:09:36 -06:00
style.appendChild(document.createTextNode(selectorStr + '\n{display:none !important;}'));
var parent = document.body || document.documentElement;
if ( parent ) {
parent.appendChild(style);
vAPI.styles.push(style);
}
hideElements(selectorStr);
messager.send({
what: 'cosmeticFiltersInjected',
type: 'cosmetic',
hostname: window.location.hostname,
selectors: selectors
});
//console.debug('µBlock> generic cosmetic filters: injecting %d CSS rules:', selectors.length, text);
};
var hideElements = (function() {
2014-07-02 10:02:29 -06:00
if ( document.body === null ) {
return function() {};
}
2015-07-06 11:53:39 -06:00
if ( document.body.shadowRoot === undefined ) {
return function(selectors) {
// https://github.com/chrisaljoudi/uBlock/issues/207
// Do not call querySelectorAll() using invalid CSS selectors
if ( selectors.length === 0 ) { return; }
var elems = document.querySelectorAll(selectors);
var i = elems.length;
if ( i === 0 ) { return; }
// https://github.com/chrisaljoudi/uBlock/issues/158
// Using CSSStyleDeclaration.setProperty is more reliable
while ( i-- ) {
elems[i].style.setProperty('display', 'none', 'important');
}
};
}
return function(selectors) {
if ( selectors.length === 0 ) { return; }
var elems = document.querySelectorAll(selectors);
var i = elems.length;
if ( i === 0 ) { return; }
// https://github.com/gorhill/uBlock/issues/435
// Using shadow content so that we do not have to modify style
// attribute.
var sessionId = vAPI.sessionId;
var elem, shadow;
while ( i-- ) {
elem = elems[i];
shadow = elem.shadowRoot;
// https://www.chromestatus.com/features/4668884095336448
// "Multiple shadow roots is being deprecated."
if ( shadow !== null ) {
if ( shadow.className !== sessionId ) {
elem.style.setProperty('display', 'none', 'important');
}
continue;
}
// https://github.com/gorhill/uBlock/pull/555
// Not all nodes can be shadowed:
// https://github.com/w3c/webcomponents/issues/102
try {
shadow = elem.createShadowRoot();
shadow.className = sessionId;
} catch (ex) {
elem.style.setProperty('display', 'none', 'important');
}
}
};
})();
2014-07-02 10:02:29 -06:00
// Extract and return the staged nodes which (may) match the selectors.
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 14:47:34 -06:00
}
return targetNodes;
2014-07-04 14:47:34 -06:00
};
// Low generics:
// - [id]
// - [class]
var processLowGenerics = function(generics, out) {
var i = generics.length;
var selector;
while ( i-- ) {
selector = generics[i];
2014-12-31 21:09:22 -07:00
if ( injectedSelectors.hasOwnProperty(selector) ) {
continue;
}
injectedSelectors[selector] = true;
out.push(selector);
2014-07-04 14:47:34 -06:00
}
};
// High-low generics:
// - [alt="..."]
// - [title="..."]
var processHighLowGenerics = function(generics, out) {
var attrs = ['title', 'alt'];
2014-08-13 18:03:55 -06:00
var attr, attrValue, nodeList, iNode, node;
var selector;
while ( (attr = attrs.pop()) ) {
nodeList = selectNodes('[' + attr + ']');
iNode = nodeList.length;
while ( iNode-- ) {
node = nodeList[iNode];
attrValue = node.getAttribute(attr);
if ( !attrValue ) { continue; }
2015-01-01 17:09:40 -07:00
// Candidate 1 = generic form
2015-01-01 17:16:02 -07:00
// If generic form is injected, no need to process the specific
2015-01-01 17:09:40 -07:00
// form, as the generic will affect all related specific forms
selector = '[' + attr + '="' + attrValue + '"]';
2015-01-01 17:09:40 -07:00
if ( generics.hasOwnProperty(selector) ) {
2014-12-31 21:09:22 -07:00
if ( injectedSelectors.hasOwnProperty(selector) === false ) {
2014-08-13 18:03:55 -06:00
injectedSelectors[selector] = true;
out.push(selector);
2015-01-01 17:09:40 -07:00
continue;
}
}
2015-01-01 17:09:40 -07:00
// Candidate 2 = specific form
2015-03-29 10:13:28 -06:00
selector = node.localName + selector;
2015-01-01 17:09:40 -07:00
if ( generics.hasOwnProperty(selector) ) {
2014-12-31 21:09:22 -07:00
if ( injectedSelectors.hasOwnProperty(selector) === false ) {
2014-08-13 18:03:55 -06:00
injectedSelectors[selector] = true;
out.push(selector);
}
}
}
2014-07-04 14:47:34 -06:00
}
};
// High-medium generics:
// - [href^="http"]
var processHighMediumGenerics = function(generics, out) {
var nodeList = selectNodes('a[href^="http"]');
var iNode = nodeList.length;
2014-08-13 18:03:55 -06:00
var node, href, pos, hash, selectors, selector, iSelector;
while ( iNode-- ) {
node = nodeList[iNode];
href = node.getAttribute('href');
if ( !href ) { continue; }
pos = href.indexOf('://');
if ( pos === -1 ) { continue; }
hash = href.slice(pos + 3, pos + 11);
2014-08-13 18:03:55 -06:00
selectors = generics[hash];
if ( selectors === undefined ) { continue; }
// A string.
if ( typeof selectors === 'string' ) {
if (
href.lastIndexOf(selectors.slice(8, -2), 0) === 0 &&
injectedSelectors.hasOwnProperty(selectors) === false
) {
injectedSelectors[selectors] = true;
out.push(selectors);
}
continue;
}
// An array of strings.
2014-08-13 18:03:55 -06:00
iSelector = selectors.length;
while ( iSelector-- ) {
selector = selectors[iSelector];
if (
href.lastIndexOf(selector.slice(8, -2), 0) === 0 &&
injectedSelectors.hasOwnProperty(selector) === false
) {
2014-08-13 18:03:55 -06:00
injectedSelectors[selector] = true;
out.push(selector);
2014-08-13 18:03:55 -06:00
}
2014-07-02 10:02:29 -06:00
}
}
};
// High-high generics are *very costly* to process, so we will coalesce
// requests to process high-high generics into as few requests as possible.
// The gain is *significant* on bloated pages.
2015-03-02 08:54:15 -07:00
var processHighHighGenericsMisses = 8;
var processHighHighGenericsTimer = null;
var processHighHighGenerics = function() {
processHighHighGenericsTimer = null;
2015-03-13 10:26:54 -06:00
if ( highGenerics.hideHigh === '' ) {
return;
}
if ( injectedSelectors.hasOwnProperty('{{highHighGenerics}}') ) {
return;
}
2015-03-01 18:26:33 -07:00
//var tStart = timer.now();
if ( document.querySelector(highGenerics.hideHigh) === null ) {
2015-03-01 18:26:33 -07:00
//console.debug('%f: high-high generic test time', timer.now() - tStart);
2015-03-02 08:54:15 -07:00
processHighHighGenericsMisses -= 1;
2015-03-01 18:26:33 -07:00
// Too many misses for these nagging highly generic CSS rules,
// so we will just skip them from now on.
2015-03-02 08:54:15 -07:00
if ( processHighHighGenericsMisses === 0 ) {
2015-03-01 18:26:33 -07:00
injectedSelectors['{{highHighGenerics}}'] = true;
2015-03-06 08:31:07 -07:00
//console.debug('high-high generic: apparently not needed...');
2015-03-01 18:26:33 -07:00
}
return;
}
2014-08-13 18:03:55 -06:00
injectedSelectors['{{highHighGenerics}}'] = true;
// We need to filter out possible exception cosmetic filters from
// high-high generics selectors.
var selectors = highGenerics.hideHigh.split(',\n');
var i = selectors.length;
2014-08-13 18:03:55 -06:00
var selector;
while ( i-- ) {
selector = selectors[i];
if ( injectedSelectors.hasOwnProperty(selector) ) {
selectors.splice(i, 1);
} else {
2014-08-13 18:03:55 -06:00
injectedSelectors[selector] = true;
2014-07-02 10:02:29 -06:00
}
}
if ( selectors.length !== 0 ) {
addStyleTag(selectors);
}
};
var processHighHighGenericsAsync = function() {
if ( processHighHighGenericsTimer !== null ) {
clearTimeout(processHighHighGenericsTimer);
}
processHighHighGenericsTimer = vAPI.setTimeout(processHighHighGenerics, 300);
2014-07-02 10:02:29 -06:00
};
// Extract all ids: these will be passed to the cosmetic filtering
// engine, and in return we will obtain only the relevant CSS selectors.
2014-07-20 13:00:26 -06:00
var idsFromNodeList = function(nodes) {
2014-07-02 10:02:29 -06:00
if ( !nodes || !nodes.length ) {
return;
2014-06-23 16:42:43 -06:00
}
2014-07-02 10:02:29 -06:00
var qq = queriedSelectors;
var ll = lowGenericSelectors;
2014-07-20 13:00:26 -06:00
var node, v;
2014-07-02 10:02:29 -06:00
var i = nodes.length;
while ( i-- ) {
node = nodes[i];
2014-07-20 13:00:26 -06:00
if ( node.nodeType !== 1 ) { continue; }
2014-07-02 10:02:29 -06:00
// id
2014-07-08 23:53:49 -06:00
v = nodes[i].id;
2014-07-20 13:00:26 -06:00
if ( typeof v !== 'string' ) { continue; }
2014-07-08 23:53:49 -06:00
v = v.trim();
2014-07-20 13:00:26 -06:00
if ( v === '' ) { continue; }
v = '#' + v;
if ( qq.hasOwnProperty(v) ) { continue; }
ll.push(v);
2014-07-20 13:00:26 -06:00
qq[v] = true;
}
};
// Extract all classes: these will be passed to the cosmetic filtering
// engine, and in return we will obtain only the relevant CSS selectors.
2015-09-04 14:30:53 -06: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
2014-07-20 13:00:26 -06:00
var classesFromNodeList = function(nodes) {
if ( !nodes || !nodes.length ) {
return;
}
2015-09-04 14:30:53 -06:00
2014-07-20 13:00:26 -06:00
var qq = queriedSelectors;
var ll = lowGenericSelectors;
2015-09-04 14:30:53 -06:00
var v, vv, len, c, beg, end;
2014-07-20 13:00:26 -06:00
var i = nodes.length;
2015-09-04 14:30:53 -06:00
2014-07-20 13:00:26 -06:00
while ( i-- ) {
2015-09-04 14:30:53 -06:00
vv = nodes[i].className;
if ( typeof vv !== 'string' ) { continue; }
len = vv.length;
beg = 0;
for (;;) {
// Skip whitespaces
while ( beg !== len ) {
c = vv.charCodeAt(beg);
if ( c !== 0x20 && (c > 0x0D || c < 0x09) ) { break; }
beg++;
}
if ( beg === len ) { break; }
end = beg + 1;
// Skip non-whitespaces
while ( end !== len ) {
c = vv.charCodeAt(end);
if ( c === 0x20 || (c <= 0x0D && c >= 0x09) ) { break; }
end++;
}
v = '.' + vv.slice(beg, end);
if ( qq.hasOwnProperty(v) === false ) {
ll.push(v);
qq[v] = true;
}
if ( end === len ) { break; }
beg = end + 1;
2014-07-02 10:02:29 -06:00
}
2014-06-23 16:42:43 -06:00
}
2014-07-02 10:02:29 -06:00
};
2014-06-23 16:42:43 -06:00
// Start cosmetic filtering.
2015-02-14 10:16:36 -07:00
idsFromNodeList(document.querySelectorAll('[id]'));
classesFromNodeList(document.querySelectorAll('[class]'));
retrieveGenericSelectors();
2014-06-23 16:42:43 -06:00
2015-03-01 18:26:33 -07:00
//console.debug('%f: uBlock: survey time', timer.now() - tStart);
// 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.
// 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-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/618
2015-03-29 10:13:28 -06:00
// Following is to observe dynamically added iframes:
// - On Firefox, the iframes fails to fire a `load` event
var ignoreTags = {
'link': true,
'script': true,
2015-03-29 10:13:28 -06:00
'style': true
};
2014-09-16 13:39:21 -06:00
// Added node lists will be cumulated here before being processed
var addedNodeLists = [];
var addedNodeListsTimer = null;
2015-03-29 10:13:28 -06:00
var collapser = uBlockCollapser;
2014-09-16 13:39:21 -06:00
2015-03-29 10:13:28 -06:00
var treeMutationObservedHandler = function() {
var nodeList, iNode, node;
while ( (nodeList = addedNodeLists.pop()) ) {
iNode = nodeList.length;
while ( iNode-- ) {
node = nodeList[iNode];
if ( node.nodeType !== 1 ) {
continue;
}
2015-03-29 10:13:28 -06:00
if ( ignoreTags.hasOwnProperty(node.localName) ) {
continue;
}
contextNodes.push(node);
2015-03-29 10:13:28 -06:00
collapser.iframesFromNode(node);
}
}
2014-09-16 13:39:21 -06:00
addedNodeListsTimer = null;
if ( contextNodes.length !== 0 ) {
idsFromNodeList(selectNodes('[id]'));
classesFromNodeList(selectNodes('[class]'));
retrieveGenericSelectors();
// This is required: a new element could be matching an already
// injected but otherwise inactive cosmetic filter. This means
// the already injected cosmetic filter become active (has an
// effect on the document), and thus must be logged if needed.
messager.send({ what: 'cosmeticFiltersActivated' });
}
2014-07-02 10:02:29 -06:00
};
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/205
2014-09-16 13:39:21 -06:00
// Do not handle added node directly from within mutation observer.
var treeMutationObservedHandlerAsync = function(mutations) {
2014-09-16 13:39:21 -06:00
var iMutation = mutations.length;
var nodeList;
while ( iMutation-- ) {
nodeList = mutations[iMutation].addedNodes;
if ( nodeList.length !== 0 ) {
2014-09-16 13:39:21 -06:00
addedNodeLists.push(nodeList);
}
}
if ( addedNodeListsTimer === null ) {
2014-12-31 21:09:22 -07:00
// I arbitrarily chose 100 ms for now:
// I have to compromise between the overhead of processing too few
2014-09-16 13:39:21 -06:00
// nodes too often and the delay of many nodes less often.
addedNodeListsTimer = vAPI.setTimeout(treeMutationObservedHandler, 100);
2014-09-16 13:39:21 -06:00
}
};
//console.debug('Starts cosmetic filtering\'s mutations observer');
// https://github.com/gorhill/httpswitchboard/issues/176
var treeObserver = new MutationObserver(treeMutationObservedHandlerAsync);
treeObserver.observe(document.body, {
childList: true,
subtree: true
});
// https://github.com/gorhill/uMatrix/issues/144
2015-04-07 17:34:22 -06:00
vAPI.shutdown.add(function() {
treeObserver.disconnect();
if ( addedNodeListsTimer !== null ) {
clearTimeout(addedNodeListsTimer);
}
});
2014-07-02 10:02:29 -06:00
})();
2014-06-23 16:42:43 -06:00
2014-08-02 09:40:27 -06:00
/******************************************************************************/
2014-06-23 16:42:43 -06:00
/******************************************************************************/
2014-09-14 14:20:40 -06:00
// Permanent
2014-06-27 15:06:42 -06:00
2015-03-29 10:13:28 -06:00
// Listener to collapse blocked resources.
// - Future requests not blocked yet
// - Elements dynamically added to the page
// - Elements which resource URL changes
2015-02-03 22:24:12 -07:00
(function() {
var onResourceFailed = function(ev) {
//console.debug('onResourceFailed(%o)', ev);
uBlockCollapser.add(ev.target);
uBlockCollapser.process();
};
document.addEventListener('error', onResourceFailed, true);
// https://github.com/gorhill/uMatrix/issues/144
2015-04-07 17:34:22 -06:00
vAPI.shutdown.add(function() {
document.removeEventListener('error', onResourceFailed, true);
});
})();
2014-06-23 16:42:43 -06:00
/******************************************************************************/
2014-09-14 14:20:40 -06:00
/******************************************************************************/
2015-04-06 19:26:05 -06:00
// https://github.com/chrisaljoudi/uBlock/issues/7
2014-09-14 14:20:40 -06:00
// Executed only once
(function() {
2015-03-29 10:13:28 -06:00
var collapser = uBlockCollapser;
var elems, i, elem;
2014-09-14 14:20:40 -06:00
2015-03-29 10:13:28 -06:00
elems = document.querySelectorAll('embed, object');
i = elems.length;
while ( i-- ) {
collapser.add(elems[i]);
}
2015-02-24 18:10:10 -07:00
2015-03-29 10:13:28 -06:00
elems = document.querySelectorAll('img');
i = elems.length;
while ( i-- ) {
elem = elems[i];
if ( elem.complete ) {
collapser.add(elem);
2015-02-24 18:10:10 -07:00
}
2015-03-29 10:13:28 -06:00
}
2015-02-24 18:10:10 -07:00
2015-03-29 10:13:28 -06:00
elems = document.querySelectorAll('iframe');
i = elems.length;
while ( i-- ) {
collapser.addIFrame(elems[i]);
2014-09-14 14:20:40 -06:00
}
2015-03-29 10:13:28 -06:00
collapser.process(0);
2014-09-14 14:20:40 -06:00
})();
/******************************************************************************/
2014-09-28 12:38:17 -06:00
/******************************************************************************/
// To send mouse coordinates to context menu handler, as the chrome API fails
// to provide the mouse position to context menu listeners.
// This could be inserted in its own content script, but it's so simple that
// I feel it's not worth the overhead.
// Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
(function() {
if ( window !== window.top ) {
return;
}
var onMouseClick = function(ev) {
messager.send({
what: 'mouseClick',
x: ev.clientX,
y: ev.clientY,
url: ev.target && ev.target.localName === 'a' ? ev.target.href : ''
2014-09-28 12:38:17 -06:00
});
};
2015-09-10 12:01:16 -06:00
window.addEventListener('mousedown', onMouseClick, true);
// https://github.com/gorhill/uMatrix/issues/144
2015-04-07 17:34:22 -06:00
vAPI.shutdown.add(function() {
2015-09-10 12:01:16 -06:00
document.removeEventListener('mousedown', onMouseClick, true);
});
2014-09-28 12:38:17 -06:00
})();
/******************************************************************************/
2015-01-01 19:14:53 -07:00
/******************************************************************************/
})();
/******************************************************************************/