@ -1,7 +1,7 @@
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
µMatrix - a Chromium browser extension to black / white list requests .
Copyright ( C ) 2014 Raymond Hill
Copyright ( C ) 2014 - 2105 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
@ -63,19 +63,19 @@ vAPI.contentscriptEndInjected = true;
var localMessager = vAPI . messaging . channel ( 'contentscript-end.js' ) ;
vAPI . shutdown . add ( function ( ) {
localMessager . close ( ) ;
} ) ;
/******************************************************************************/
/******************************************************************************/
// This is to be executed only once: putting this code in its own closure
// means the code will be flushed from memory once executed.
( function ( ) {
// Unrendered noscript (because CSP) workaround
/******************************************************************************/
// Executed once.
/*------------[ Unrendered Noscript (because CSP) Workaround ]----------------*/
var checkScriptBlacklistedHandler = function ( response ) {
( function ( ) {
var checkScriptBlacklistedHandler = function ( response ) {
if ( ! response . scriptBlacklisted ) {
return ;
}
@ -88,28 +88,33 @@ var checkScriptBlacklistedHandler = function(response) {
fakeNoscript . innerHTML = '<!-- uMatrix NOSCRIPT tag replacement: see <https://github.com/gorhill/httpswitchboard/issues/177> -->\n' + realNoscript . textContent ;
realNoscript . parentNode . replaceChild ( fakeNoscript , realNoscript ) ;
}
} ;
} ;
localMessager . send ( {
localMessager . send ( {
what : 'checkScriptBlacklisted' ,
url : window . location . href
} , checkScriptBlacklistedHandler ) ;
} , checkScriptBlacklistedHandler ) ;
} ) ( ) ;
/******************************************************************************/
/******************************************************************************/
// Executed only once.
var localStorageHandler = function ( mustRemove ) {
( function ( ) {
var localStorageHandler = function ( mustRemove ) {
if ( mustRemove ) {
window . localStorage . clear ( ) ;
window . sessionStorage . clear ( ) ;
// console.debug('HTTP Switchboard > found and removed non-empty localStorage');
}
} ;
} ;
// Check with extension whether local storage must be emptied
// rhill 2014-03-28: we need an exception handler in case 3rd-party access
// to site data is disabled.
// https://github.com/gorhill/httpswitchboard/issues/215
try {
// Check with extension whether local storage must be emptied
// rhill 2014-03-28: we need an exception handler in case 3rd-party access
// to site data is disabled.
// https://github.com/gorhill/httpswitchboard/issues/215
try {
var hasLocalStorage = window . localStorage && window . localStorage . length ;
var hasSessionStorage = window . sessionStorage && window . sessionStorage . length ;
if ( hasLocalStorage || hasSessionStorage ) {
@ -132,19 +137,169 @@ try {
// "There is no way to enumerate or delete the databases available for an origin from this API."
// Ref.: http://www.w3.org/TR/webdatabase/#databases
}
}
catch ( e ) {
}
/******************************************************************************/
}
catch ( e ) {
}
} ) ( ) ;
/******************************************************************************/
/******************************************************************************/
( function ( ) {
// https://github.com/gorhill/uMatrix/issues/45
var collapser = ( function ( ) {
var timer = null ;
var requestId = 1 ;
var newRequests = [ ] ;
var pendingRequests = { } ;
var pendingRequestCount = 0 ;
var srcProps = {
'iframe' : 'src' ,
'img' : 'src'
} ;
var PendingRequest = function ( target , tagName , attr ) {
this . id = requestId ++ ;
this . target = target ;
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 ( requests ) {
if ( requests === null || Array . isArray ( requests ) === false ) {
return ;
}
var i = requests . length ;
var request , entry ;
while ( i -- ) {
request = requests [ i ] ;
if ( pendingRequests . hasOwnProperty ( request . id ) === false ) {
continue ;
}
entry = pendingRequests [ request . id ] ;
delete pendingRequests [ request . id ] ;
pendingRequestCount -= 1 ;
// https://github.com/chrisaljoudi/uBlock/issues/869
if ( ! request . collapse ) {
continue ;
}
// https://github.com/chrisaljoudi/uBlock/issues/399
// Never remove elements from the DOM, just hide them
entry . target . style . setProperty ( 'display' , 'none' , '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 ( ) {
timer = null ;
localMessager . send ( {
what : 'evaluateURLs' ,
requests : newRequests
} , onProcessed ) ;
newRequests = [ ] ;
} ;
var process = function ( delay ) {
if ( newRequests . length === 0 ) {
return ;
}
if ( delay === 0 ) {
clearTimeout ( timer ) ;
send ( ) ;
} else if ( timer === null ) {
timer = setTimeout ( send , delay || 50 ) ;
}
} ;
var addNode = function ( target ) {
var tagName = target . localName ;
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 , tagName , prop ) ;
newRequests . push ( new BouncingRequest ( req . id , tagName , src ) ) ;
} ;
var addNodes = function ( nodes ) {
var node ;
var i = nodes . length ;
while ( i -- ) {
node = nodes [ i ] ;
if ( node . nodeType === 1 ) {
addNode ( node ) ;
}
}
} ;
var addBranches = function ( branches ) {
var root ;
var i = branches . length ;
while ( i -- ) {
root = branches [ i ] ;
if ( root . nodeType === 1 ) {
addNode ( root ) ;
// blocked images will be reported by onResourceFailed
addNodes ( root . 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 ) {
addNode ( ev . target ) ;
process ( ) ;
} ;
document . addEventListener ( 'error' , onResourceFailed , true ) ;
vAPI . shutdown . add ( function ( ) {
if ( timer !== null ) {
clearTimeout ( timer ) ;
timer = null ;
}
document . removeEventListener ( 'error' , onResourceFailed , true ) ;
newRequests = [ ] ;
pendingRequests = { } ;
pendingRequestCount = 0 ;
} ) ;
return {
addNodes : addNodes ,
addBranches : addBranches ,
process : process
} ;
} ) ( ) ;
/******************************************************************************/
/******************************************************************************/
var nodesAddedHandler = function ( nodeList , summary ) {
@ -154,17 +309,17 @@ var nodesAddedHandler = function(nodeList, summary) {
if ( node . nodeType !== 1 ) {
continue ;
}
if ( typeof node . tag Name !== 'string' ) {
if ( typeof node . local Name !== 'string' ) {
continue ;
}
switch ( node . tagName . toUpperCase ( ) ) {
switch ( node . localName ) {
case 'SCRIPT ' :
case 'script ' :
// https://github.com/gorhill/httpswitchboard/issues/252
// Do not count µMatrix's own script tags, they are not required
// to "unbreak" a web page
if ( node . id && node . id . i ndexOf( 'uMatrix-' ) === 0 ) {
if ( typeof node . id === 'string' && node . id . lastI ndexOf( 'uMatrix-' , 0 ) === 0 ) {
break ;
}
text = node . textContent . trim ( ) ;
@ -179,14 +334,14 @@ var nodesAddedHandler = function(nodeList, summary) {
}
break ;
case 'A ' :
if ( node . href . i ndexOf( 'javascript: ' ) === 0 ) {
case 'a ' :
if ( node . href . lastI ndexOf( 'javascript' , 0 ) === 0 ) {
summary . scriptSources [ '{inline_script}' ] = true ;
summary . mustReport = true ;
}
break ;
case 'OBJECT ' :
case 'object ' :
src = ( node . data || '' ) . trim ( ) ;
if ( src !== '' ) {
summary . pluginSources [ src ] = true ;
@ -194,7 +349,7 @@ var nodesAddedHandler = function(nodeList, summary) {
}
break ;
case 'EMBED ' :
case 'embed ' :
src = ( node . src || '' ) . trim ( ) ;
if ( src !== '' ) {
summary . pluginSources [ src ] = true ;
@ -221,14 +376,19 @@ var nodeListsAddedHandler = function(nodeLists) {
} ;
while ( i -- ) {
nodesAddedHandler ( nodeLists [ i ] , summary ) ;
collapser . addBranches ( nodeLists [ i ] ) ;
}
if ( summary . mustReport ) {
localMessager . send ( summary ) ;
}
collapser . process ( ) ;
} ;
/******************************************************************************/
/******************************************************************************/
// Executed only once.
( function ( ) {
var summary = {
what : 'contentScriptSummary' ,
@ -241,30 +401,36 @@ var nodeListsAddedHandler = function(nodeLists) {
// &
// Looks for inline javascript also in at least one a[href] element.
// https://github.com/gorhill/httpswitchboard/issues/131
nodesAddedHandler ( document . querySelectorAll ( 'script, a[href^="javascript:"], object, embed ' ) , summary ) ;
nodesAddedHandler ( document . querySelectorAll ( 'a[href^="javascript:"],embed,object,script ' ) , summary ) ;
//console.debug('contentscript-end.js > firstObservationHandler(): found %d script tags in "%s"', Object.keys(summary.scriptSources).length, window.location.href);
localMessager . send ( summary ) ;
collapser . addNodes ( document . querySelectorAll ( 'iframe,img' ) ) ;
collapser . process ( ) ;
} ) ( ) ;
/******************************************************************************/
/******************************************************************************/
// Observe changes in the DOM
// Added node lists will be cumulated here before being processed
var addedNodeLists = [ ] ;
var addedNodeListsTimer = null ;
var treeMutationObservedHandler = function ( ) {
( function ( ) {
var addedNodeLists = [ ] ;
var addedNodeListsTimer = null ;
var treeMutationObservedHandler = function ( ) {
nodeListsAddedHandler ( addedNodeLists ) ;
addedNodeListsTimer = null ;
addedNodeLists = [ ] ;
} ;
} ;
// https://github.com/gorhill/uBlock/issues/205
// Do not handle added node directly from within mutation observer.
var treeMutationObservedHandlerAsync = function ( mutations ) {
// https://github.com/gorhill/uBlock/issues/205
// Do not handle added node directly from within mutation observer.
var treeMutationObservedHandlerAsync = function ( mutations ) {
var iMutation = mutations . length ;
var nodeList ;
while ( iMutation -- ) {
@ -280,21 +446,41 @@ var treeMutationObservedHandlerAsync = function(mutations) {
if ( addedNodeListsTimer === null ) {
addedNodeListsTimer = setTimeout ( treeMutationObservedHandler , 250 ) ;
}
} ;
} ;
// This fixes http://acid3.acidtests.org/
if ( document . body ) {
return ;
}
// This fixes http://acid3.acidtests.org/
if ( document . body ) {
// https://github.com/gorhill/httpswitchboard/issues/176
var treeObserver = new MutationObserver ( treeMutationObservedHandlerAsync ) ;
treeObserver . observe ( document . body , {
childList : true ,
subtree : true
} ) ;
}
vAPI . shutdown . add ( function ( ) {
if ( addedNodeListsTimer !== null ) {
clearTimeout ( addedNodeListsTimer ) ;
addedNodeListsTimer = null ;
}
if ( treeObserver !== null ) {
treeObserver . disconnect ( ) ;
treeObserver = null ;
}
addedNodeLists = [ ] ;
} ) ;
} ) ( ) ;
/******************************************************************************/
/******************************************************************************/
} ) ( ) ;
localMessager . send ( { what : 'shutdown?' } , function ( response ) {
if ( response === true ) {
vAPI . shutdown . exec ( ) ;
}
} ) ;
/******************************************************************************/
/******************************************************************************/