1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-09-14 23:12:28 +02:00

more refactoring of content script: better modularization of various components

This commit is contained in:
gorhill 2016-08-12 08:55:35 -04:00
parent 01da277886
commit 6fd0bb4291
11 changed files with 790 additions and 705 deletions

View File

@ -67,6 +67,23 @@ if ( vAPI.sessionId ) {
/******************************************************************************/ /******************************************************************************/
var referenceCounter = 0;
vAPI.lock = function() {
referenceCounter += 1;
};
vAPI.unlock = function() {
referenceCounter -= 1;
if ( referenceCounter === 0 ) {
// Eventually there will be code here to flush the javascript code
// from this file out of memory when it ends up unused.
}
};
/******************************************************************************/
vAPI.executionCost = { vAPI.executionCost = {
start: function(){}, start: function(){},
stop: function(){} stop: function(){}

View File

@ -56,6 +56,18 @@ var vAPI = self.vAPI = self.vAPI || {};
/******************************************************************************/ /******************************************************************************/
var referenceCounter = 0;
vAPI.lock = function() {
referenceCounter += 1;
};
vAPI.unlock = function() {
referenceCounter -= 1;
};
/******************************************************************************/
vAPI.executionCost = { vAPI.executionCost = {
start: function(){}, start: function(){},
stop: function(){} stop: function(){}

View File

@ -21,9 +21,50 @@
'use strict'; 'use strict';
/******************************************************************************/ /*******************************************************************************
// Injected into content pages +--> [[domSurveyor] --> domFilterer]
domWatcher--|
+--> [domCollapser]
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.
If page is whitelisted:
- domWatcher: off
- domCollapser: off
- domFilterer: off
- domSurveyor: off
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
If generic cosmetic filtering is disabled:
- domWatcher: on
- domCollapser: on
- domFilterer: on
- domSurveyor: off
Additionally, the domSurveyor can turn itself off once it decides that
it has become pointless (repeatedly not finding new cosmetic filters).
*/
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
@ -32,14 +73,13 @@
// Abort execution by throwing if an unexpected condition arise. // Abort execution by throwing if an unexpected condition arise.
// - https://github.com/chrisaljoudi/uBlock/issues/456 // - https://github.com/chrisaljoudi/uBlock/issues/456
if ( typeof vAPI !== 'object' || vAPI.contentscriptInjected ) { if ( typeof vAPI !== 'object' ) {
throw new Error('uBlock Origin: aborting content scripts for ' + window.location); throw new Error('uBlock Origin: aborting content scripts for ' + window.location);
} }
vAPI.lock();
vAPI.executionCost.start(); vAPI.executionCost.start();
vAPI.contentscriptInjected = true;
vAPI.matchesProp = (function() { vAPI.matchesProp = (function() {
var docElem = document.documentElement; var docElem = document.documentElement;
if ( typeof docElem.matches !== 'function' ) { if ( typeof docElem.matches !== 'function' ) {
@ -119,16 +159,16 @@ var jobQueue = [
var reParserEx = /:(?:matches-css|has|style|xpath)\(.+?\)$/; var reParserEx = /:(?:matches-css|has|style|xpath)\(.+?\)$/;
var allExceptions = Object.create(null); var allExceptions = Object.create(null),
var allSelectors = Object.create(null); allSelectors = Object.create(null),
var stagedNodes = []; stagedNodes = [],
var matchesProp = vAPI.matchesProp; matchesProp = vAPI.matchesProp;
// Complex selectors, due to their nature may need to be "de-committed". A // Complex selectors, due to their nature may need to be "de-committed". A
// Set() is used to implement this functionality. // Set() is used to implement this functionality.
var complexSelectorsOldResultSet; var complexSelectorsOldResultSet,
var complexSelectorsCurrentResultSet = new Set(); complexSelectorsCurrentResultSet = new Set();
/******************************************************************************/ /******************************************************************************/
@ -245,6 +285,7 @@ var domFilterer = {
enabled: true, enabled: true,
hiddenId: vAPI.randomToken(), hiddenId: vAPI.randomToken(),
hiddenNodeCount: 0, hiddenNodeCount: 0,
loggerEnabled: undefined,
styleTags: [], styleTags: [],
jobQueue: jobQueue, jobQueue: jobQueue,
@ -352,6 +393,10 @@ var domFilterer = {
}, },
commit_: function() { commit_: function() {
if ( stagedNodes.length === 0 ) {
return;
}
var beforeHiddenNodeCount = this.hiddenNodeCount, var beforeHiddenNodeCount = this.hiddenNodeCount,
styleText = '', i, n; styleText = '', i, n;
@ -423,7 +468,11 @@ var domFilterer = {
} }
// If DOM nodes have been affected, lazily notify core process. // If DOM nodes have been affected, lazily notify core process.
if ( commitHit && cosmeticFiltersActivatedTimer === null ) { if (
this.loggerEnabled !== false &&
commitHit &&
cosmeticFiltersActivatedTimer === null
) {
cosmeticFiltersActivatedTimer = vAPI.setTimeout( cosmeticFiltersActivatedTimer = vAPI.setTimeout(
cosmeticFiltersActivated, cosmeticFiltersActivated,
503 503
@ -512,6 +561,10 @@ var domFilterer = {
} }
}, },
toggleLogging: function(state) {
this.loggerEnabled = state;
},
toggleOff: function() { toggleOff: function() {
this.enabled = false; this.enabled = false;
}, },
@ -583,49 +636,54 @@ return domFilterer;
(function domIsLoading() { (function domIsLoading() {
// Domain-based ABP cosmetic filters. var responseHandler = function(response) {
// These can be inserted before the DOM is loaded. // cosmetic filtering engine aka 'cfe'
var cfeDetails = response && response.specificCosmeticFilters;
var cosmeticFilters = function(details) { if ( !cfeDetails || !cfeDetails.ready ) {
var domFilterer = vAPI.domFilterer; vAPI.domWatcher = vAPI.domCollapser = vAPI.domFilterer =
domFilterer.addExceptions(details.cosmeticDonthide); vAPI.domSurveyor = vAPI.domIsLoaded = null;
// https://github.com/chrisaljoudi/uBlock/issues/143 vAPI.unlock();
domFilterer.addSelectors(details.cosmeticHide);
domFilterer.commit(undefined, true);
};
var netFilters = function(details) {
var parent = document.head || document.documentElement;
if ( !parent ) {
return; return;
} }
var styleTag = document.createElement('style');
styleTag.setAttribute('type', 'text/css'); vAPI.executionCost.start();
var text = details.netHide.join(',\n');
var css = details.netCollapse ? if ( response.noCosmeticFiltering ) {
vAPI.domFilterer = null;
vAPI.domSurveyor = null;
} else {
var domFilterer = vAPI.domFilterer;
domFilterer.toggleLogging(response.loggerEnabled);
if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) {
vAPI.domSurveyor = null;
}
if ( cfeDetails.cosmeticHide.length !== 0 || cfeDetails.cosmeticDonthide.length !== 0 ) {
domFilterer.addExceptions(cfeDetails.cosmeticDonthide);
domFilterer.addSelectors(cfeDetails.cosmeticHide);
domFilterer.commit(undefined, true);
}
}
var parent = document.head || document.documentElement;
if ( parent ) {
var elem, text;
if ( cfeDetails.netHide.length !== 0 ) {
elem = document.createElement('style');
elem.setAttribute('type', 'text/css');
text = cfeDetails.netHide.join(',\n');
text += response.collapseBlocked ?
'\n{display:none !important;}' : '\n{display:none !important;}' :
'\n{visibility:hidden !important;}'; '\n{visibility:hidden !important;}';
styleTag.appendChild(document.createTextNode(text + css)); elem.appendChild(document.createTextNode(text));
parent.appendChild(styleTag); parent.appendChild(elem);
};
// 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'); // Library of resources is located at:
// https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
if ( cfeDetails.scripts ) {
elem = document.createElement('script');
// Have the injected script tag remove itself when execution completes: // Have the injected script tag remove itself when execution completes:
// to keep DOM as clean as possible. // to keep DOM as clean as possible.
scripts += text = cfeDetails.scripts +
"\n" + "\n" +
"(function() {\n" + "(function() {\n" +
" var c = document.currentScript,\n" + " var c = document.currentScript,\n" +
@ -634,38 +692,21 @@ return domFilterer;
" p.removeChild(c);\n" + " p.removeChild(c);\n" +
" }\n" + " }\n" +
"})();"; "})();";
scriptTag.appendChild(document.createTextNode(scripts)); elem.appendChild(document.createTextNode(text));
parent.appendChild(scriptTag); parent.appendChild(elem);
vAPI.injectedScripts = scripts; vAPI.injectedScripts = text;
};
var responseHandler = function(details) {
vAPI.executionCost.start();
if ( details ) {
vAPI.skipCosmeticFiltering = details.skipCosmeticFiltering;
vAPI.skipCosmeticSurveying = details.skipCosmeticSurveying;
if (
(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 // https://github.com/chrisaljoudi/uBlock/issues/587
// If no filters were found, maybe the script was injected before // If no filters were found, maybe the script was injected before
// uBlock's process was fully initialized. When this happens, pages // uBlock's process was fully initialized. When this happens, pages
// won't be cleaned right after browser launch. // won't be cleaned right after browser launch.
vAPI.contentscriptInjected = details && details.ready; if ( document.readyState !== 'loading' ) {
window.requestAnimationFrame(vAPI.domIsLoaded);
} else {
document.addEventListener('DOMContentLoaded', vAPI.domIsLoaded);
}
vAPI.executionCost.stop('domIsLoading/responseHandler'); vAPI.executionCost.stop('domIsLoading/responseHandler');
}; };
@ -674,7 +715,7 @@ return domFilterer;
vAPI.messaging.send( vAPI.messaging.send(
'contentscript', 'contentscript',
{ {
what: 'retrieveDomainCosmeticSelectors', what: 'retrieveContentScriptParameters',
pageURL: url, pageURL: url,
locationURL: url locationURL: url
}, },
@ -687,7 +728,137 @@ return domFilterer;
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
var domCollapser = (function() { vAPI.domWatcher = (function() {
var domLayoutObserver = null,
ignoreTags = { 'head': 1, 'link': 1, 'meta': 1, 'script': 1, 'style': 1 },
addedNodeLists = [],
addedNodes = [],
removedNodes = false,
safeObserverHandlerTimer = null,
listeners = [];
var safeObserverHandler = function() {
vAPI.executionCost.start();
safeObserverHandlerTimer = null;
var i = addedNodeLists.length,
nodeList, iNode, node;
while ( i-- ) {
nodeList = addedNodeLists[i];
iNode = nodeList.length;
while ( iNode-- ) {
node = nodeList[iNode];
if ( node.nodeType !== 1 ) {
continue;
}
if ( ignoreTags[node.localName] === 1 ) {
continue;
}
addedNodes.push(node);
}
}
addedNodeLists.length = 0;
if ( addedNodes.length !== 0 ) {
listeners[0](addedNodes, removedNodes);
if ( listeners[1] ) {
listeners[1](addedNodes, removedNodes);
}
addedNodes.length = 0;
removedNodes = false;
}
vAPI.executionCost.stop('domWatcher/safeObserverHandler');
};
// https://github.com/chrisaljoudi/uBlock/issues/205
// Do not handle added node directly from within mutation observer.
var observerHandler = function(mutations) {
vAPI.executionCost.start();
var nodeList, mutation,
i = mutations.length;
while ( i-- ) {
mutation = mutations[i];
nodeList = mutation.addedNodes;
if ( nodeList.length !== 0 ) {
addedNodeLists.push(nodeList);
}
if ( mutation.removedNodes.length !== 0 ) {
removedNodes = true;
}
}
if ( (addedNodeLists.length !== 0 || removedNodes) && safeObserverHandlerTimer === null ) {
safeObserverHandlerTimer = window.requestAnimationFrame(safeObserverHandler);
}
vAPI.executionCost.stop('domWatcher/observerHandler');
};
var addListener = function(listener) {
if ( listeners.indexOf(listener) !== -1 ) {
return;
}
listeners.push(listener);
if ( domLayoutObserver !== null ) {
return;
}
domLayoutObserver = new MutationObserver(observerHandler);
domLayoutObserver.observe(document.documentElement, {
//attributeFilter: [ 'class', 'id' ],
//attributes: true,
childList: true,
subtree: true
});
};
var removeListener = function(listener) {
var pos = listeners.indexOf(listener);
if ( pos === -1 ) {
return;
}
listeners.splice(pos, 1);
if ( listeners.length !== 0 || domLayoutObserver === null ) {
return;
}
domLayoutObserver.disconnect();
domLayoutObserver = null;
};
var stop = function() {
if ( domLayoutObserver !== null ) {
domLayoutObserver.disconnect();
domLayoutObserver = null;
}
if ( safeObserverHandlerTimer !== null ) {
window.cancelAnimationFrame(safeObserverHandlerTimer);
}
};
var start = function() {
// Observe changes in the DOM only if...
// - there is a document.body
// - there is at least one `script` tag
if ( document.body === null || document.querySelector('script') === null ) {
vAPI.domWatcher = null;
return;
}
// https://github.com/gorhill/uMatrix/issues/144
vAPI.shutdown.add(stop);
};
return {
addListener: addListener,
removeListener: removeListener,
start: start
};
})();
/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
vAPI.domCollapser = (function() {
var timer = null; var timer = null;
var pendingRequests = Object.create(null); var pendingRequests = Object.create(null);
var roundtripRequests = []; var roundtripRequests = [];
@ -913,18 +1084,64 @@ var domCollapser = (function() {
} }
}; };
var iframesFromNode = function(node) { var domChangedHandler = function(nodes) {
var node;
for ( var i = 0, ni = nodes.length; i < ni; i++ ) {
node = nodes[i];
if ( node.localName === 'iframe' ) { if ( node.localName === 'iframe' ) {
addIFrame(node); addIFrame(node);
process();
} }
if ( node.children.length !== 0 ) { if ( node.children.length !== 0 ) {
var iframes = node.getElementsByTagName('iframe'); var iframes = node.getElementsByTagName('iframe');
if ( iframes.length !== 0 ) { if ( iframes.length !== 0 ) {
addIFrames(iframes); addIFrames(iframes);
}
}
}
process(); process();
};
var onResourceFailed = function(ev) {
vAPI.executionCost.start();
vAPI.domCollapser.add(ev.target);
vAPI.domCollapser.process();
vAPI.executionCost.stop('domIsLoaded/onResourceFailed');
};
var start = function() {
// Listener to collapse blocked resources.
// - Future requests not blocked yet
// - Elements dynamically added to the page
// - Elements which resource URL changes
// https://github.com/chrisaljoudi/uBlock/issues/7
// Preferring getElementsByTagName over querySelectorAll:
// http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
var elems = document.images || document.getElementsByTagName('img'),
i = elems.length, elem;
while ( i-- ) {
elem = elems[i];
if ( elem.complete ) {
add(elem);
} }
} }
addMany(document.embeds || document.getElementsByTagName('embed'));
addMany(document.getElementsByTagName('object'));
addIFrames(document.getElementsByTagName('iframe'));
process(0);
document.addEventListener('error', onResourceFailed, true);
if ( vAPI.domWatcher ) {
vAPI.domWatcher.addListener(domChangedHandler);
}
// https://github.com/gorhill/uMatrix/issues/144
vAPI.shutdown.add(function() {
document.removeEventListener('error', onResourceFailed, true);
if ( vAPI.domWatcher ) {
vAPI.domWatcher.removeListener(domChangedHandler);
}
});
}; };
return { return {
@ -932,8 +1149,8 @@ var domCollapser = (function() {
addMany: addMany, addMany: addMany,
addIFrame: addIFrame, addIFrame: addIFrame,
addIFrames: addIFrames, addIFrames: addIFrames,
iframesFromNode: iframesFromNode, process: process,
process: process start: start
}; };
})(); })();
@ -941,62 +1158,20 @@ var domCollapser = (function() {
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
var domIsLoaded = function(ev) { vAPI.domSurveyor = (function() {
/******************************************************************************/
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;
}
vAPI.executionCost.start();
/*******************************************************************************
skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
skip-survey=true: commit
*/
// Cosmetic filtering.
(function() {
if ( vAPI.skipCosmeticFiltering ) {
//console.debug('Abort cosmetic filtering');
return;
}
// https://github.com/chrisaljoudi/uBlock/issues/789 // https://github.com/chrisaljoudi/uBlock/issues/789
// https://github.com/gorhill/uBlock/issues/873 // https://github.com/gorhill/uBlock/issues/873
// Be sure that our style tags used for cosmetic filtering are still // Be sure that our style tags used for cosmetic filtering are still
// applied. // applied.
var domFilterer = vAPI.domFilterer;
domFilterer.checkStyleTags(false);
domFilterer.commit();
var contextNodes = [ document.documentElement ], var domFilterer = null,
messaging = vAPI.messaging; messaging = vAPI.messaging,
surveyPhase3Nodes = [],
var domSurveyor = (function() { cosmeticSurveyingMissCount = 0,
if ( vAPI.skipCosmeticSurveying === true ) {
return;
}
var cosmeticSurveyingMissCount = 0,
highGenerics = null, highGenerics = null,
lowGenericSelectors = [], lowGenericSelectors = [],
queriedSelectors = Object.create(null); queriedSelectors = Object.create(null),
removedNodesHandlerMissCount = 0;
// Handle main process' response. // Handle main process' response.
@ -1050,22 +1225,20 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
// Shutdown surveyor if too many consecutive empty resultsets. // Shutdown surveyor if too many consecutive empty resultsets.
if ( domFilterer.job0._0.length === 0 ) { if ( domFilterer.job0._0.length === 0 ) {
cosmeticSurveyingMissCount += 1; cosmeticSurveyingMissCount += 1;
if ( cosmeticSurveyingMissCount > 255 ) {
domSurveyor = undefined;
}
} else { } else {
cosmeticSurveyingMissCount = 0; cosmeticSurveyingMissCount = 0;
} }
domFilterer.commit(contextNodes); domFilterer.commit(surveyPhase3Nodes);
contextNodes = []; surveyPhase3Nodes = [];
vAPI.executionCost.stop('domIsLoaded/surveyPhase2'); vAPI.executionCost.stop('domIsLoaded/surveyPhase2');
}; };
// Query main process. // Query main process.
var surveyPhase2 = function() { var surveyPhase2 = function(addedNodes) {
surveyPhase3Nodes = surveyPhase3Nodes.concat(addedNodes);
if ( lowGenericSelectors.length !== 0 || highGenerics === null ) { if ( lowGenericSelectors.length !== 0 || highGenerics === null ) {
messaging.send( messaging.send(
'contentscript', 'contentscript',
@ -1100,7 +1273,7 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
var attr, attrValue, nodeList, iNode, node; var attr, attrValue, nodeList, iNode, node;
var selector; var selector;
while ( (attr = attrs.pop()) ) { while ( (attr = attrs.pop()) ) {
nodeList = selectNodes('[' + attr + ']'); nodeList = selectNodes('[' + attr + ']', surveyPhase3Nodes);
iNode = nodeList.length; iNode = nodeList.length;
while ( iNode-- ) { while ( iNode-- ) {
node = nodeList[iNode]; node = nodeList[iNode];
@ -1128,7 +1301,7 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
// - [href^="http"] // - [href^="http"]
var processHighMediumGenerics = function(generics) { var processHighMediumGenerics = function(generics) {
var stagedNodes = contextNodes, var stagedNodes = surveyPhase3Nodes,
i = stagedNodes.length; i = stagedNodes.length;
if ( i === 1 && stagedNodes[0] === document.documentElement ) { if ( i === 1 && stagedNodes[0] === document.documentElement ) {
processHighMediumGenericsForNodes(document.links, generics); processHighMediumGenericsForNodes(document.links, generics);
@ -1191,7 +1364,7 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
) { ) {
tstart = window.performance.now(); tstart = window.performance.now();
var matchesProp = vAPI.matchesProp, var matchesProp = vAPI.matchesProp,
nodes = contextNodes, nodes = surveyPhase3Nodes,
i = nodes.length, node; i = nodes.length, node;
while ( i-- ) { while ( i-- ) {
node = nodes[i]; node = nodes[i];
@ -1216,7 +1389,6 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
if ( document.querySelector(highGenerics.hideHighComplex) !== null ) { if ( document.querySelector(highGenerics.hideHighComplex) !== null ) {
highHighComplexGenericsInjected = true; highHighComplexGenericsInjected = true;
domFilterer.addSelectors(highGenerics.hideHighComplex.split(',\n')); domFilterer.addSelectors(highGenerics.hideHighComplex.split(',\n'));
domFilterer.commit();
} }
highHighComplexGenericsCost += window.performance.now() - tstart; highHighComplexGenericsCost += window.performance.now() - tstart;
} }
@ -1224,8 +1396,8 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
// Extract and return the staged nodes which (may) match the selectors. // Extract and return the staged nodes which (may) match the selectors.
var selectNodes = function(selector) { var selectNodes = function(selector, nodes) {
var stagedNodes = contextNodes, var stagedNodes = nodes,
i = stagedNodes.length; i = stagedNodes.length;
if ( i === 1 && stagedNodes[0] === document.documentElement ) { if ( i === 1 && stagedNodes[0] === document.documentElement ) {
return document.querySelectorAll(selector); return document.querySelectorAll(selector);
@ -1252,8 +1424,8 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
// http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens // http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens
// http://jsperf.com/enumerate-classes/6 // http://jsperf.com/enumerate-classes/6
var surveyPhase1 = function() { var surveyPhase1 = function(addedNodes) {
var nodes = selectNodes('[class],[id]'); var nodes = selectNodes('[class],[id]', addedNodes);
var qq = queriedSelectors; var qq = queriedSelectors;
var ll = lowGenericSelectors; var ll = lowGenericSelectors;
var node, v, vv, j; var node, v, vv, j;
@ -1289,209 +1461,80 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
} }
} }
} }
surveyPhase2(); surveyPhase2(addedNodes);
}; };
return surveyPhase1; var domChangedHandler = function(addedNodes, removedNodes) {
})(); if ( cosmeticSurveyingMissCount < 256 ) {
surveyPhase1(addedNodes);
// Start cosmetic filtering.
if ( domSurveyor ) {
domSurveyor();
}
//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;
}
// https://github.com/chrisaljoudi/uBlock/issues/618
// Following is to observe dynamically added iframes:
// - On Firefox, the iframes fails to fire a `load` event
var ignoreTags = {
'link': true,
'meta': true,
'script': true,
'style': true
};
// Added node lists will be cumulated here before being processed
var addedNodeLists = [],
addedNodeListsTimer = null,
removedNodeListsTimer = null,
removedNodesHandlerMissCount = 0,
collapser = domCollapser;
var addedNodesHandler = function() {
vAPI.executionCost.start();
addedNodeListsTimer = null;
var iNodeList = addedNodeLists.length,
nodeList, iNode, node;
while ( iNodeList-- ) {
nodeList = addedNodeLists[iNodeList];
iNode = nodeList.length;
while ( iNode-- ) {
node = nodeList[iNode];
if ( node.nodeType !== 1 ) {
continue;
}
if ( ignoreTags.hasOwnProperty(node.localName) ) {
continue;
}
contextNodes.push(node);
collapser.iframesFromNode(node);
}
}
addedNodeLists.length = 0;
if ( contextNodes.length !== 0 ) {
if ( domSurveyor ) {
domSurveyor();
} else { } else {
domFilterer.commit(contextNodes); domFilterer.commit(addedNodes);
contextNodes = [];
if ( domFilterer.commitMissCount > 255 ) {
domLayoutObserver.disconnect();
} }
}
}
vAPI.executionCost.stop('domIsLoaded/addedNodesHandler');
};
// https://github.com/gorhill/uBlock/issues/873 // https://github.com/gorhill/uBlock/issues/873
// This will ensure our style elements will stay in the DOM. // This will ensure our style elements will stay in the DOM.
var removedNodesHandler = function() { if ( removedNodes && removedNodesHandlerMissCount < 16 ) {
if ( domFilterer.checkStyleTags(true) === false ) { if ( domFilterer.checkStyleTags(true) === false ) {
removedNodesHandlerMissCount += 1; removedNodesHandlerMissCount += 1;
} }
if ( removedNodesHandlerMissCount < 16 ) {
removedNodeListsTimer = null;
} }
}; };
// https://github.com/chrisaljoudi/uBlock/issues/205 var start = function() {
// Do not handle added node directly from within mutation observer. domFilterer = vAPI.domFilterer;
// I arbitrarily chose 100 ms for now: I have to compromise between the if ( domFilterer === null ) {
// overhead of processing too few nodes too often and the delay of many return;
// nodes less often. }
var domLayoutChanged = function(mutations) { domFilterer.checkStyleTags(false);
domFilterer.commit();
domChangedHandler([ document.documentElement ]);
if ( vAPI.domWatcher ) {
vAPI.domWatcher.addListener(domChangedHandler);
}
};
return {
start: start
};
})();
/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
vAPI.domIsLoaded = function(ev) {
if ( ev ) {
document.removeEventListener('DOMContentLoaded', vAPI.domIsLoaded);
}
vAPI.domIsLoaded = null;
// I've seen this happens on Firefox
if ( window.location === null ) {
return;
}
vAPI.executionCost.start(); vAPI.executionCost.start();
var removedNodeLists = false; if ( vAPI.domWatcher ) {
var iMutation = mutations.length; vAPI.domWatcher.start();
var nodeList, mutation;
while ( iMutation-- ) {
mutation = mutations[iMutation];
nodeList = mutation.addedNodes;
if ( nodeList.length !== 0 ) {
addedNodeLists.push(nodeList);
}
if ( mutation.removedNodes.length !== 0 ) {
removedNodeLists = true;
}
}
if ( addedNodeLists.length !== 0 && addedNodeListsTimer === null ) {
addedNodeListsTimer = window.requestAnimationFrame(addedNodesHandler);
}
if ( removedNodeLists && removedNodeListsTimer === null ) {
removedNodeListsTimer = window.requestAnimationFrame(removedNodesHandler);
} }
vAPI.executionCost.stop('domIsLoaded/domLayoutChanged'); if ( vAPI.domCollapser ) {
}; vAPI.domCollapser.start();
//console.debug('Starts cosmetic filtering\'s mutations observer');
// https://github.com/gorhill/httpswitchboard/issues/176
var domLayoutObserver = new MutationObserver(domLayoutChanged);
domLayoutObserver.observe(document.documentElement, {
childList: true,
subtree: true
});
// https://github.com/gorhill/uMatrix/issues/144
vAPI.shutdown.add(function() {
domLayoutObserver.disconnect();
if ( addedNodeListsTimer !== null ) {
window.cancelAnimationFrame(addedNodeListsTimer);
} }
if ( removedNodeListsTimer !== null ) {
window.cancelAnimationFrame(removedNodeListsTimer); if ( vAPI.domFilterer && vAPI.domSurveyor ) {
vAPI.domSurveyor.start();
} }
});
})();
/******************************************************************************/
// Permanent
// Listener to collapse blocked resources.
// - Future requests not blocked yet
// - Elements dynamically added to the page
// - Elements which resource URL changes
(function() {
var onResourceFailed = function(ev) {
vAPI.executionCost.start();
domCollapser.add(ev.target);
domCollapser.process();
vAPI.executionCost.stop('domIsLoaded/onResourceFailed');
};
document.addEventListener('error', onResourceFailed, true);
// https://github.com/gorhill/uMatrix/issues/144
vAPI.shutdown.add(function() {
document.removeEventListener('error', onResourceFailed, true);
});
})();
/******************************************************************************/
// https://github.com/chrisaljoudi/uBlock/issues/7
// Executed only once.
// Preferring getElementsByTagName over querySelectorAll:
// http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
(function() {
var collapser = domCollapser,
elems = document.images || document.getElementsByTagName('img'),
i = elems.length, elem;
while ( i-- ) {
elem = elems[i];
if ( elem.complete ) {
collapser.add(elem);
}
}
collapser.addMany(document.embeds || document.getElementsByTagName('embed'));
collapser.addMany(document.getElementsByTagName('object'));
collapser.addIFrames(document.getElementsByTagName('iframe'));
collapser.process(0);
})();
/******************************************************************************/
// To send mouse coordinates to main process, as the chrome API fails // To send mouse coordinates to main process, as the chrome API fails
// to provide the mouse position to context menu listeners. // to provide the mouse position to context menu listeners.
// https://github.com/chrisaljoudi/uBlock/issues/1143 // https://github.com/chrisaljoudi/uBlock/issues/1143
// Also, find a link under the mouse, to try to avoid confusing new tabs // Also, find a link under the mouse, to try to avoid confusing new tabs
// as nuisance popups. // as nuisance popups.
// Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu // Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
(function() { (function() {
if ( window !== window.top ) { if ( window !== window.top || !vAPI.domFilterer ) {
return; return;
} }
@ -1522,20 +1565,11 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
}); });
})(); })();
/******************************************************************************/
vAPI.executionCost.stop('domIsLoaded'); vAPI.executionCost.stop('domIsLoaded');
}; };
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
if ( document.readyState !== 'loading' ) {
window.requestAnimationFrame(domIsLoaded);
} else {
document.addEventListener('DOMContentLoaded', domIsLoaded);
}
vAPI.executionCost.stop('contentscript.js'); vAPI.executionCost.stop('contentscript.js');

View File

@ -1550,16 +1550,17 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) {
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.retrieveDomainSelectors = function(request) { FilterContainer.prototype.retrieveDomainSelectors = function(request, noCosmeticFiltering) {
if ( !request.locationURL ) { if ( !request.locationURL ) {
return; return;
} }
//quickProfiler.start('FilterContainer.retrieve()'); //quickProfiler.start('FilterContainer.retrieve()');
var hostname = this.µburi.hostnameFromURI(request.locationURL); var hostname = this.µburi.hostnameFromURI(request.locationURL),
var domain = this.µburi.domainFromHostname(hostname) || hostname; domain = this.µburi.domainFromHostname(hostname) || hostname,
var pos = domain.indexOf('.'); pos = domain.indexOf('.'),
cacheEntry = this.selectorCache[hostname];
// https://github.com/chrisaljoudi/uBlock/issues/587 // https://github.com/chrisaljoudi/uBlock/issues/587
// r.ready will tell the content script the cosmetic filtering engine is // r.ready will tell the content script the cosmetic filtering engine is
@ -1572,15 +1573,14 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
ready: this.frozen, ready: this.frozen,
domain: domain, domain: domain,
entity: pos === -1 ? domain : domain.slice(0, pos - domain.length), entity: pos === -1 ? domain : domain.slice(0, pos - domain.length),
skipCosmeticSurveying: false, noDOMSurveying: false,
skipCosmeticFiltering: this.acceptedCount === 0,
cosmeticHide: [], cosmeticHide: [],
cosmeticDonthide: this.genericDonthide.slice(), cosmeticDonthide: [],
netHide: [], netHide: [],
netCollapse: µb.userSettings.collapseBlocked,
scripts: this.retrieveScriptTags(domain, hostname) scripts: this.retrieveScriptTags(domain, hostname)
}; };
if ( !noCosmeticFiltering ) {
var hash, bucket; var hash, bucket;
hash = makeHash(0, domain, this.domainHashMask); hash = makeHash(0, domain, this.domainHashMask);
if ( (bucket = this.hostnameFilters[hash]) ) { if ( (bucket = this.hostnameFilters[hash]) ) {
@ -1596,7 +1596,15 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
if ( this.entityFilters.hasOwnProperty(r.entity) ) { if ( this.entityFilters.hasOwnProperty(r.entity) ) {
r.cosmeticHide = r.cosmeticHide.concat(this.entityFilters[r.entity]); r.cosmeticHide = r.cosmeticHide.concat(this.entityFilters[r.entity]);
} }
// No entity exceptions as of now
// cached cosmetic filters.
if ( cacheEntry ) {
cacheEntry.retrieve('cosmetic', r.cosmeticHide);
r.noDOMSurveying = cacheEntry.cosmeticSurveyingMissCount > cosmeticSurveyingMissCountMax;
}
// Exception cosmetic filters.
r.cosmeticDonthide = this.genericDonthide.slice();
hash = makeHash(1, domain, this.domainHashMask); hash = makeHash(1, domain, this.domainHashMask);
if ( (bucket = this.hostnameFilters[hash]) ) { if ( (bucket = this.hostnameFilters[hash]) ) {
@ -1608,22 +1616,16 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
if ( (bucket = this.hostnameFilters[this.type1NoDomainHash]) ) { if ( (bucket = this.hostnameFilters[this.type1NoDomainHash]) ) {
bucket.retrieve(hostname, r.cosmeticDonthide); bucket.retrieve(hostname, r.cosmeticDonthide);
} }
// No entity exceptions as of now
}
var cacheEntry = this.selectorCache[hostname]; // Collapsible blocked resources.
if ( cacheEntry ) { if ( cacheEntry ) {
cacheEntry.retrieve('cosmetic', r.cosmeticHide);
cacheEntry.retrieve('net', r.netHide); cacheEntry.retrieve('net', r.netHide);
r.skipCosmeticSurveying = cacheEntry.cosmeticSurveyingMissCount > cosmeticSurveyingMissCountMax;
} }
//quickProfiler.stop(); //quickProfiler.stop();
//console.log(
// 'µBlock> abp-hide-filters.js: "%s" => %d selectors out',
// request.locationURL,
// r.cosmeticHide.length + r.cosmeticDonthide.length
//);
return r; return r;
}; };

View File

@ -518,12 +518,18 @@ var onMessage = function(request, sender, callback) {
} }
switch ( request.what ) { switch ( request.what ) {
case 'retrieveDomainCosmeticSelectors': case 'retrieveContentScriptParameters':
if ( pageStore && pageStore.getNetFilteringSwitch() ) { if ( pageStore && pageStore.getNetFilteringSwitch() ) {
response = µb.cosmeticFilteringEngine.retrieveDomainSelectors(request); response = {
if ( response && response.skipCosmeticFiltering !== true ) { loggerEnabled: µb.logger.isEnabled(),
response.skipCosmeticFiltering = !pageStore.getSpecificCosmeticFilteringSwitch(); collapseBlocked: µb.userSettings.collapseBlocked,
} noCosmeticFiltering: µb.cosmeticFilteringEngine.acceptedCount === 0 || pageStore.noCosmeticFiltering === true,
noGenericCosmeticFiltering: pageStore.noGenericCosmeticFiltering === true
};
response.specificCosmeticFilters = µb.cosmeticFilteringEngine.retrieveDomainSelectors(
request,
response.noCosmeticFiltering
);
} }
break; break;

View File

@ -310,14 +310,28 @@ PageStore.prototype.init = function(tabId) {
this.largeMediaTimer = null; this.largeMediaTimer = null;
this.netFilteringCache = NetFilteringResultCache.factory(); this.netFilteringCache = NetFilteringResultCache.factory();
// Support `elemhide` filter option. Called at this point so the required this.noCosmeticFiltering = µb.hnSwitches.evaluateZ('no-cosmetic-filtering', tabContext.rootHostname) === true;
// context is all setup at this point. if ( µb.logger.isEnabled() && this.noCosmeticFiltering ) {
this.skipCosmeticFiltering = µb.staticNetFilteringEngine.matchStringExactType( µb.logger.writeOne(
tabId,
'cosmetic',
µb.hnSwitches.toResultString(),
'dom',
tabContext.rawURL,
this.tabHostname,
this.tabHostname
);
}
// Support `generichide` filter option.
this.noGenericCosmeticFiltering = this.noCosmeticFiltering;
if ( this.noGenericCosmeticFiltering !== true ) {
this.noGenericCosmeticFiltering = µb.staticNetFilteringEngine.matchStringExactType(
this.createContextFromPage(), this.createContextFromPage(),
tabContext.normalURL, tabContext.normalURL,
'elemhide' 'elemhide'
) === false; ) === false;
if ( this.skipCosmeticFiltering && µb.logger.isEnabled() ) { if ( µb.logger.isEnabled() && this.noGenericCosmeticFiltering ) {
// https://github.com/gorhill/uBlock/issues/370 // https://github.com/gorhill/uBlock/issues/370
// Log using `cosmetic-filtering`, not `elemhide`. // Log using `cosmetic-filtering`, not `elemhide`.
µb.logger.writeOne( µb.logger.writeOne(
@ -330,6 +344,8 @@ PageStore.prototype.init = function(tabId) {
this.tabHostname this.tabHostname
); );
} }
}
return this; return this;
}; };
@ -458,16 +474,14 @@ PageStore.prototype.getNetFilteringSwitch = function() {
/******************************************************************************/ /******************************************************************************/
PageStore.prototype.getSpecificCosmeticFilteringSwitch = function() { PageStore.prototype.getSpecificCosmeticFilteringSwitch = function() {
var tabContext = µb.tabContextManager.lookup(this.tabId); return this.noCosmeticFiltering !== true;
return tabContext !== null &&
µb.hnSwitches.evaluateZ('no-cosmetic-filtering', tabContext.rootHostname) !== true;
}; };
/******************************************************************************/ /******************************************************************************/
PageStore.prototype.getGenericCosmeticFilteringSwitch = function() { PageStore.prototype.getGenericCosmeticFilteringSwitch = function() {
return this.skipCosmeticFiltering !== true && return this.noGenericCosmeticFiltering !== true &&
this.getSpecificCosmeticFilteringSwitch(); this.noCosmeticFiltering !== true;
}; };
/******************************************************************************/ /******************************************************************************/

View File

@ -27,7 +27,7 @@
/******************************************************************************/ /******************************************************************************/
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
return; return;
} }

View File

@ -24,7 +24,7 @@
/******************************************************************************/ /******************************************************************************/
(function() { (function() {
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
return; return;
} }

View File

@ -24,7 +24,7 @@
/******************************************************************************/ /******************************************************************************/
(function() { (function() {
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
return; return;
} }

View File

@ -24,7 +24,7 @@
/******************************************************************************/ /******************************************************************************/
(function() { (function() {
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
return; return;
} }

View File

@ -28,7 +28,7 @@
/******************************************************************************/ /******************************************************************************/
if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) {
return; return;
} }