@ -109,71 +109,86 @@ vAPI.contentscriptEndInjected = true;
// https://github.com/gorhill/uMatrix/issues/45
// https://github.com/gorhill/uMatrix/issues/45
var collapser = ( function ( ) {
var collapser = ( function ( ) {
var timer = null ;
var resquestIdGenerator = 1 ,
var requestId = 1 ;
processTimer ,
var newRequests = [ ] ;
toProcess = [ ] ,
var pendingRequests = { } ;
toFilter = [ ] ,
var pendingRequestCount = 0 ;
toCollapse = new Map ( ) ,
var srcProps = {
cachedBlockedMap ,
'img' : 'src'
cachedBlockedMapHash ,
cachedBlockedMapTimer ,
reURLPlaceholder = /\{\{url\}\}/g ;
var src1stProps = {
'embed' : 'src' ,
'iframe' : 'src' ,
'img' : 'src' ,
'object' : 'data'
} ;
} ;
var reURLplaceholder = /\{\{url\}\}/g ;
var src2ndProps = {
'img' : 'srcset'
var PendingRequest = function ( target ) {
this . id = requestId ++ ;
this . target = target ;
pendingRequests [ this . id ] = this ;
pendingRequestCount += 1 ;
} ;
} ;
var tagToTypeMap = {
// Because a while ago I have observed constructors are faster than
embed : 'media' ,
// literal object instanciations.
iframe : 'frame' ,
var BouncingRequest = function ( id , tagName , url ) {
img : 'image' ,
this . id = id ;
object : 'media'
this . tagName = tagName ;
} ;
this . url = url ;
var cachedBlockedSetClear = function ( ) {
this . blocked = false ;
cachedBlockedMap =
cachedBlockedMapHash =
cachedBlockedMapTimer = undefined ;
} ;
} ;
// https://github.com/chrisaljoudi/uBlock/issues/174
// Do not remove fragment from src URL
var onProcessed = function ( response ) {
var onProcessed = function ( response ) {
if ( ! response ) {
if ( ! response ) { // This happens if uBO is disabled or restarted.
toCollapse . clear ( ) ;
return ;
return ;
}
}
var requests = response . requests ;
if ( requests === null || Array . isArray ( requests ) === false ) {
var targets = toCollapse . get ( response . id ) ;
if ( targets === undefined ) { return ; }
toCollapse . delete ( response . id ) ;
if ( cachedBlockedMapHash !== response . hash ) {
cachedBlockedMap = new Map ( response . blockedResources ) ;
cachedBlockedMapHash = response . hash ;
if ( cachedBlockedMapTimer !== undefined ) {
clearTimeout ( cachedBlockedMapTimer ) ;
}
cachedBlockedMapTimer = vAPI . setTimeout ( cachedBlockedSetClear , 30000 ) ;
}
if ( cachedBlockedMap === undefined || cachedBlockedMap . size === 0 ) {
return ;
return ;
}
}
var collapse = response . collapse ;
var placeholders = response . placeholders ;
var i = requests . length ;
var request , entry , target , tagName , docurl , replaced ;
while ( i -- ) {
request = requests [ i ] ;
if ( pendingRequests . hasOwnProperty ( request . id ) === false ) {
continue ;
}
entry = pendingRequests [ request . id ] ;
delete pendingRequests [ request . id ] ;
pendingRequestCount -= 1 ;
// Not blocked
var placeholders = response . placeholders ,
if ( ! request . blocked ) {
tag , prop , src , collapsed , docurl , replaced ;
continue ;
for ( var target of targets ) {
tag = target . localName ;
prop = src1stProps [ tag ] ;
if ( prop === undefined ) { continue ; }
src = target [ prop ] ;
if ( typeof src !== 'string' || src . length === 0 ) {
prop = src2ndProps [ tag ] ;
if ( prop === undefined ) { continue ; }
src = target [ prop ] ;
if ( typeof src !== 'string' || src . length === 0 ) { continue ; }
}
}
collapsed = cachedBlockedMap . get ( tagToTypeMap [ tag ] + ' ' + src ) ;
target = entry . target ;
if ( collapsed === undefined ) { continue ; }
if ( collapsed ) {
// No placeholders
if ( collapse ) {
target . style . setProperty ( 'display' , 'none' , 'important' ) ;
target . style . setProperty ( 'display' , 'none' , 'important' ) ;
target . hidden = true ;
continue ;
continue ;
}
}
if ( tag === 'iframe' ) {
tagName = target . localName ;
docurl =
'data:text/html,' +
// Special case: iframe
encodeURIComponent (
if ( tagName === 'iframe' ) {
placeholders . iframe . replace ( reURLPlaceholder , src )
docurl = 'data:text/html,' + encodeURIComponent ( placeholders . iframe . replace ( reURLplaceholder , request . url ) ) ;
) ;
replaced = false ;
replaced = false ;
// Using contentWindow.location prevent tainting browser
// Using contentWindow.location prevent tainting browser
// history -- i.e. breaking back button (seen on Chromium).
// history -- i.e. breaking back button (seen on Chromium).
@ -189,148 +204,125 @@ var collapser = (function() {
}
}
continue ;
continue ;
}
}
target . setAttribute ( src1stProps [ tag ] , placeholders [ tag ] ) ;
// Everything else
target . setAttribute ( srcProps [ tagName ] , placeholders [ tagName ] ) ;
target . style . setProperty ( 'border' , placeholders . border , 'important' ) ;
target . style . setProperty ( 'border' , placeholders . border , 'important' ) ;
target . style . setProperty ( 'background' , placeholders . background , 'important' ) ;
target . style . setProperty ( 'background' , placeholders . background , 'important' ) ;
}
}
// 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 ( ) {
var send = function ( ) {
timer = null ;
processTimer = undefined ;
vAPI . messaging . send ( 'contentscript.js' , {
toCollapse . set ( resquestIdGenerator , toProcess ) ;
what : 'evaluateURLs' ,
var msg = {
requests : newRequests
what : 'lookupBlockedCollapsibles' ,
} , onProcessed ) ;
id : resquestIdGenerator ,
newRequests = [ ] ;
toFilter : toFilter ,
hash : cachedBlockedMapHash
} ;
vAPI . messaging . send ( 'contentscript.js' , msg , onProcessed ) ;
toProcess = [ ] ;
toFilter = [ ] ;
resquestIdGenerator += 1 ;
} ;
} ;
var process = function ( delay ) {
var process = function ( delay ) {
if ( newRequests . length === 0 ) {
if ( toProcess . length === 0 ) { return ; }
return ;
}
if ( delay === 0 ) {
if ( delay === 0 ) {
clearTimeout ( timer ) ;
if ( processTimer !== undefined ) {
clearTimeout ( processTimer ) ;
}
send ( ) ;
send ( ) ;
} else if ( timer === null ) {
} else if ( processTimer === undefined ) {
timer = vAPI . setTimeout ( send , delay || 50 ) ;
processTimer = vAPI . setTimeout ( send , delay || 47 ) ;
}
} ;
var add = function ( target ) {
toProcess . push ( target ) ;
} ;
var addMany = function ( targets ) {
var i = targets . length ;
while ( i -- ) {
toProcess . push ( targets [ i ] ) ;
}
}
} ;
} ;
var iframeSourceModified = function ( mutations ) {
var iframeSourceModified = function ( mutations ) {
var i = mutations . length ;
var i = mutations . length ;
while ( i -- ) {
while ( i -- ) {
addFrameNode ( mutations [ i ] . target , true ) ;
add I Frame( mutations [ i ] . target , true ) ;
}
}
process ( ) ;
process ( ) ;
} ;
} ;
var iframeSourceObserver = null ;
var iframeSourceObserver ;
var iframeSourceObserverOptions = {
var iframeSourceObserverOptions = {
attributes : true ,
attributes : true ,
attributeFilter : [ 'src' ]
attributeFilter : [ 'src' ]
} ;
} ;
var add FrameNod e = function ( iframe , dontObserve ) {
var add I Frame = function ( iframe , dontObserve ) {
// https://github.com/gorhill/uBlock/issues/162
// https://github.com/gorhill/uBlock/issues/162
// Be prepared to deal with possible change of src attribute.
// Be prepared to deal with possible change of src attribute.
if ( dontObserve !== true ) {
if ( dontObserve !== true ) {
if ( iframeSourceObserver === null ) {
if ( iframeSourceObserver === undefined ) {
iframeSourceObserver = new MutationObserver ( iframeSourceModified ) ;
iframeSourceObserver = new MutationObserver ( iframeSourceModified ) ;
}
}
iframeSourceObserver . observe ( iframe , iframeSourceObserverOptions ) ;
iframeSourceObserver . observe ( iframe , iframeSourceObserverOptions ) ;
}
}
// https://github.com/chrisaljoudi/uBlock/issues/174
// Do not remove fragment from src URL
var src = iframe . src ;
var src = iframe . src ;
if ( src . lastIndexOf ( 'http' , 0 ) !== 0 ) {
if ( src === '' || typeof src !== 'string' ) { return ; }
return ;
if ( src . startsWith ( 'http' ) === false ) { return ; }
}
toFilter . push ( { type : 'frame' , url : iframe . src } ) ;
var req = new PendingRequest ( iframe ) ;
add ( iframe ) ;
newRequests . push ( new BouncingRequest ( req . id , 'iframe' , src ) ) ;
} ;
var addNode = function ( target ) {
var tagName = target . localName ;
if ( tagName === 'iframe' ) {
addFrameNode ( target ) ;
return ;
}
var prop = srcProps [ tagName ] ;
if ( prop === undefined ) {
return ;
}
// https://github.com/chrisaljoudi/uBlock/issues/174
// Do not remove fragment from src URL
var src = target [ prop ] ;
if ( typeof src !== 'string' || src === '' ) {
return ;
}
if ( src . lastIndexOf ( 'http' , 0 ) !== 0 ) {
return ;
}
var req = new PendingRequest ( target ) ;
newRequests . push ( new BouncingRequest ( req . id , tagName , src ) ) ;
} ;
} ;
var addNodes = function ( nodes ) {
var addIFrames = function ( iframes ) {
var node ;
var i = iframes . length ;
var i = nodes . length ;
while ( i -- ) {
while ( i -- ) {
node = nodes [ i ] ;
addIFrame ( iframes [ i ] ) ;
if ( node . nodeType === 1 ) {
addNode ( node ) ;
}
}
}
} ;
} ;
var add Branches = function ( branches ) {
var addNodeList = function ( nodeList ) {
var root;
var node ,
var i = branches . length ;
i = nodeList . length ;
while ( i -- ) {
while ( i -- ) {
root = branches [ i ] ;
node = nodeList [ i ] ;
if ( root . nodeType === 1 ) {
if ( node . nodeType !== 1 ) { continue ; }
addNode ( root ) ;
if ( node . localName === 'iframe' ) {
// blocked images will be reported by onResourceFailed
addIFrame ( node ) ;
addNodes ( root . querySelectorAll ( 'iframe' ) ) ;
}
if ( node . childElementCount !== 0 ) {
addIFrames ( node . querySelectorAll ( 'iframe' ) ) ;
}
}
}
}
} ;
} ;
// Listener to collapse blocked resources.
// - Future requests not blocked yet
// - Elements dynamically added to the page
// - Elements which resource URL changes
var onResourceFailed = function ( ev ) {
var onResourceFailed = function ( ev ) {
addNode ( ev . target ) ;
if ( tagToTypeMap [ ev . target . localName ] !== undefined ) {
process ( ) ;
add ( ev . target ) ;
process ( ) ;
}
} ;
} ;
document . addEventListener ( 'error' , onResourceFailed , true ) ;
document . addEventListener ( 'error' , onResourceFailed , true ) ;
vAPI . shutdown . add ( function ( ) {
vAPI . shutdown . add ( function ( ) {
if ( timer !== null ) {
document . removeEventListener ( 'error' , onResourceFailed , true ) ;
clearTimeout ( timer ) ;
if ( iframeSourceObserver !== undefined ) {
timer = null ;
}
if ( iframeSourceObserver !== null ) {
iframeSourceObserver . disconnect ( ) ;
iframeSourceObserver . disconnect ( ) ;
iframeSourceObserver = null ;
iframeSourceObserver = undefined ;
}
if ( processTimer !== undefined ) {
clearTimeout ( processTimer ) ;
processTimer = undefined ;
}
}
document . removeEventListener ( 'error' , onResourceFailed , true ) ;
newRequests = [ ] ;
pendingRequests = { } ;
pendingRequestCount = 0 ;
} ) ;
} ) ;
return {
return {
addNodes : addNodes ,
addMany : addMany ,
addBranches : addBranches ,
addIFrames : addIFrames ,
addNodeList : addNodeList ,
process : process
process : process
} ;
} ;
} ) ( ) ;
} ) ( ) ;
@ -345,10 +337,6 @@ var hasInlineScript = function(nodeList, summary) {
if ( node . nodeType !== 1 ) {
if ( node . nodeType !== 1 ) {
continue ;
continue ;
}
}
if ( typeof node . localName !== 'string' ) {
continue ;
}
if ( node . localName === 'script' ) {
if ( node . localName === 'script' ) {
text = node . textContent . trim ( ) ;
text = node . textContent . trim ( ) ;
if ( text === '' ) {
if ( text === '' ) {
@ -357,7 +345,6 @@ var hasInlineScript = function(nodeList, summary) {
summary . inlineScript = true ;
summary . inlineScript = true ;
break ;
break ;
}
}
if ( node . localName === 'a' && node . href . lastIndexOf ( 'javascript' , 0 ) === 0 ) {
if ( node . localName === 'a' && node . href . lastIndexOf ( 'javascript' , 0 ) === 0 ) {
summary . inlineScript = true ;
summary . inlineScript = true ;
break ;
break ;
@ -368,8 +355,6 @@ var hasInlineScript = function(nodeList, summary) {
}
}
} ;
} ;
/******************************************************************************/
var nodeListsAddedHandler = function ( nodeLists ) {
var nodeListsAddedHandler = function ( nodeLists ) {
var i = nodeLists . length ;
var i = nodeLists . length ;
if ( i === 0 ) {
if ( i === 0 ) {
@ -385,7 +370,7 @@ var nodeListsAddedHandler = function(nodeLists) {
if ( summary . inlineScript === false ) {
if ( summary . inlineScript === false ) {
hasInlineScript ( nodeLists [ i ] , summary ) ;
hasInlineScript ( nodeLists [ i ] , summary ) ;
}
}
collapser . add Branches ( nodeLists [ i ] ) ;
collapser . add NodeList ( nodeLists [ i ] ) ;
}
}
if ( summary . mustReport ) {
if ( summary . mustReport ) {
vAPI . messaging . send ( 'contentscript.js' , summary ) ;
vAPI . messaging . send ( 'contentscript.js' , summary ) ;
@ -415,7 +400,8 @@ var nodeListsAddedHandler = function(nodeLists) {
vAPI . messaging . send ( 'contentscript.js' , summary ) ;
vAPI . messaging . send ( 'contentscript.js' , summary ) ;
collapser . addNodes ( document . querySelectorAll ( 'iframe,img' ) ) ;
collapser . addMany ( document . querySelectorAll ( 'img' ) ) ;
collapser . addIFrames ( document . querySelectorAll ( 'iframe' ) ) ;
collapser . process ( ) ;
collapser . process ( ) ;
} ) ( ) ;
} ) ( ) ;
@ -427,6 +413,9 @@ var nodeListsAddedHandler = function(nodeLists) {
// Added node lists will be cumulated here before being processed
// Added node lists will be cumulated here before being processed
( function ( ) {
( function ( ) {
// This fixes http://acid3.acidtests.org/
if ( ! document . body ) { return ; }
var addedNodeLists = [ ] ;
var addedNodeLists = [ ] ;
var addedNodeListsTimer = null ;
var addedNodeListsTimer = null ;
@ -439,28 +428,19 @@ var nodeListsAddedHandler = function(nodeLists) {
// https://github.com/gorhill/uBlock/issues/205
// https://github.com/gorhill/uBlock/issues/205
// Do not handle added node directly from within mutation observer.
// Do not handle added node directly from within mutation observer.
var treeMutationObservedHandlerAsync = function ( mutations ) {
var treeMutationObservedHandlerAsync = function ( mutations ) {
var iMutation = mutations . length ;
var iMutation = mutations . length ,
var nodeList ;
nodeList ;
while ( iMutation -- ) {
while ( iMutation -- ) {
nodeList = mutations [ iMutation ] . addedNodes ;
nodeList = mutations [ iMutation ] . addedNodes ;
if ( nodeList . length !== 0 ) {
if ( nodeList . length !== 0 ) {
addedNodeLists . push ( nodeList ) ;
addedNodeLists . push ( nodeList ) ;
}
}
}
}
// I arbitrarily chose 250 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. There is nothing
// time critical here.
if ( addedNodeListsTimer === null ) {
if ( addedNodeListsTimer === null ) {
addedNodeListsTimer = vAPI . setTimeout ( treeMutationObservedHandler , 250 ) ;
addedNodeListsTimer = vAPI . setTimeout ( treeMutationObservedHandler , 47 ) ;
}
}
} ;
} ;
// This fixes http://acid3.acidtests.org/
if ( ! document . body ) {
return ;
}
// https://github.com/gorhill/httpswitchboard/issues/176
// https://github.com/gorhill/httpswitchboard/issues/176
var treeObserver = new MutationObserver ( treeMutationObservedHandlerAsync ) ;
var treeObserver = new MutationObserver ( treeMutationObservedHandlerAsync ) ;
treeObserver . observe ( document . body , {
treeObserver . observe ( document . body , {