1
0
Fork 0
mirror of https://github.com/gorhill/uMatrix.git synced 2024-06-02 02:14:52 +12:00
uMatrix/src/js/contentscript.js

537 lines
17 KiB
JavaScript
Raw Normal View History

2014-10-18 08:01:09 +13:00
/*******************************************************************************
uMatrix - a Chromium browser extension to black/white list requests.
Copyright (C) 2014-2017 Raymond Hill
2014-10-18 08:01:09 +13:00
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uMatrix
*/
/* global HTMLDocument */
2014-10-18 08:01:09 +13:00
'use strict';
2014-10-18 08:01:09 +13:00
/******************************************************************************/
/******************************************************************************/
2015-04-12 09:15:57 +12:00
// Injected into content pages
2014-10-18 08:01:09 +13:00
2015-04-12 09:15:57 +12:00
(function() {
2014-10-18 08:01:09 +13:00
2015-04-12 09:15:57 +12:00
/******************************************************************************/
2014-10-18 08:01:09 +13:00
2015-04-12 09:15:57 +12:00
// https://github.com/chrisaljoudi/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
//console.debug('contentscript.js > not a HTLMDocument');
return;
}
// This can also happen (for example if script injected into a `data:` URI doc)
if ( !window.location ) {
return;
2015-04-12 09:15:57 +12:00
}
2014-10-18 08:01:09 +13:00
2015-04-12 09:15:57 +12:00
// This can happen
2015-05-18 07:00:37 +12:00
if ( typeof vAPI !== 'object' ) {
//console.debug('contentscript.js > vAPI not found');
2015-04-12 09:15:57 +12:00
return;
}
2014-10-18 08:01:09 +13:00
2015-04-12 09:15:57 +12:00
// https://github.com/chrisaljoudi/uBlock/issues/456
// Already injected?
if ( vAPI.contentscriptEndInjected ) {
//console.debug('contentscript.js > content script already injected');
2015-04-12 09:15:57 +12:00
return;
}
vAPI.contentscriptEndInjected = true;
2014-10-18 08:01:09 +13:00
/******************************************************************************/
/******************************************************************************/
// Executed only once.
2014-10-18 08:01:09 +13:00
(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 {
var hasLocalStorage = window.localStorage && window.localStorage.length;
var hasSessionStorage = window.sessionStorage && window.sessionStorage.length;
if ( hasLocalStorage || hasSessionStorage ) {
vAPI.messaging.send('contentscript.js', {
what: 'contentScriptHasLocalStorage',
url: window.location.href
}, localStorageHandler);
}
// TODO: indexedDB
//if ( window.indexedDB && !!window.indexedDB.webkitGetDatabaseNames ) {
// var db = window.indexedDB.webkitGetDatabaseNames().onsuccess = function(sender) {
// console.debug('webkitGetDatabaseNames(): result=%o', sender.target.result);
// };
//}
// TODO: Web SQL
2017-07-21 01:32:30 +12:00
// if ( window.openDatabase ) {
// Sad:
// "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
2017-07-21 01:32:30 +12:00
// }
2014-10-18 08:01:09 +13:00
}
catch (e) {
2014-10-18 08:01:09 +13:00
}
})();
2014-10-18 08:01:09 +13:00
/******************************************************************************/
2014-10-18 08:01:09 +13:00
/******************************************************************************/
// https://github.com/gorhill/uMatrix/issues/45
var collapser = (function() {
var resquestIdGenerator = 1,
processTimer,
toProcess = [],
toFilter = [],
toCollapse = new Map(),
cachedBlockedMap,
cachedBlockedMapHash,
cachedBlockedMapTimer,
reURLPlaceholder = /\{\{url\}\}/g;
var src1stProps = {
'embed': 'src',
'iframe': 'src',
'img': 'src',
'object': 'data'
};
var src2ndProps = {
'img': 'srcset'
};
var tagToTypeMap = {
embed: 'media',
iframe: 'frame',
img: 'image',
object: 'media'
};
var cachedBlockedSetClear = function() {
cachedBlockedMap =
cachedBlockedMapHash =
cachedBlockedMapTimer = undefined;
};
// https://github.com/chrisaljoudi/uBlock/issues/174
// Do not remove fragment from src URL
2015-05-03 04:07:40 +12:00
var onProcessed = function(response) {
if ( !response ) { // This happens if uBO is disabled or restarted.
toCollapse.clear();
return;
}
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;
}
2015-05-04 00:18:06 +12:00
var placeholders = response.placeholders,
tag, prop, src, collapsed, docurl, replaced;
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);
if ( collapsed === undefined ) { continue; }
if ( collapsed ) {
target.style.setProperty('display', 'none', 'important');
target.hidden = true;
2015-05-03 23:33:24 +12:00
continue;
2015-05-03 04:07:40 +12:00
}
if ( tag === 'iframe' ) {
docurl =
'data:text/html,' +
encodeURIComponent(
placeholders.iframe.replace(reURLPlaceholder, src)
);
2016-11-16 04:05:05 +13:00
replaced = false;
// Using contentWindow.location prevent tainting browser
// history -- i.e. breaking back button (seen on Chromium).
if ( target.contentWindow ) {
2016-11-16 04:05:05 +13:00
try {
target.contentWindow.location.replace(docurl);
replaced = true;
} catch(ex) {
}
}
if ( !replaced ) {
target.setAttribute('src', docurl);
}
2015-05-04 00:18:06 +12:00
continue;
2015-05-03 23:58:10 +12:00
}
target.setAttribute(src1stProps[tag], placeholders[tag]);
2015-05-04 00:18:06 +12:00
target.style.setProperty('border', placeholders.border, 'important');
target.style.setProperty('background', placeholders.background, 'important');
}
};
var send = function() {
processTimer = undefined;
toCollapse.set(resquestIdGenerator, toProcess);
var msg = {
what: 'lookupBlockedCollapsibles',
id: resquestIdGenerator,
toFilter: toFilter,
hash: cachedBlockedMapHash
};
vAPI.messaging.send('contentscript.js', msg, onProcessed);
toProcess = [];
toFilter = [];
resquestIdGenerator += 1;
};
2014-10-18 08:01:09 +13:00
var process = function(delay) {
if ( toProcess.length === 0 ) { return; }
if ( delay === 0 ) {
if ( processTimer !== undefined ) {
clearTimeout(processTimer);
}
send();
} else if ( processTimer === undefined ) {
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]);
}
};
2014-10-18 08:01:09 +13:00
2015-05-03 23:10:05 +12:00
var iframeSourceModified = function(mutations) {
var i = mutations.length;
while ( i-- ) {
addIFrame(mutations[i].target, true);
2015-05-03 23:10:05 +12:00
}
process();
};
var iframeSourceObserver;
2015-05-03 23:10:05 +12:00
var iframeSourceObserverOptions = {
attributes: true,
attributeFilter: [ 'src' ]
};
var addIFrame = function(iframe, dontObserve) {
2015-05-03 23:10:05 +12:00
// https://github.com/gorhill/uBlock/issues/162
// Be prepared to deal with possible change of src attribute.
if ( dontObserve !== true ) {
if ( iframeSourceObserver === undefined ) {
2015-05-04 01:06:35 +12:00
iframeSourceObserver = new MutationObserver(iframeSourceModified);
}
2015-05-03 23:10:05 +12:00
iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
}
var src = iframe.src;
if ( src === '' || typeof src !== 'string' ) { return; }
if ( src.startsWith('http') === false ) { return; }
toFilter.push({ type: 'frame', url: iframe.src });
add(iframe);
};
2014-10-18 08:01:09 +13:00
var addIFrames = function(iframes) {
var i = iframes.length;
while ( i-- ) {
addIFrame(iframes[i]);
}
};
2014-10-18 08:01:09 +13:00
var addNodeList = function(nodeList) {
var node,
i = nodeList.length;
while ( i-- ) {
node = nodeList[i];
if ( node.nodeType !== 1 ) { continue; }
if ( node.localName === 'iframe' ) {
addIFrame(node);
}
if ( node.childElementCount !== 0 ) {
addIFrames(node.querySelectorAll('iframe'));
}
}
};
2014-10-18 08:01:09 +13:00
var onResourceFailed = function(ev) {
if ( tagToTypeMap[ev.target.localName] !== undefined ) {
add(ev.target);
process();
}
};
document.addEventListener('error', onResourceFailed, true);
vAPI.shutdown.add(function() {
document.removeEventListener('error', onResourceFailed, true);
if ( iframeSourceObserver !== undefined ) {
2015-05-04 01:06:35 +12:00
iframeSourceObserver.disconnect();
iframeSourceObserver = undefined;
}
if ( processTimer !== undefined ) {
clearTimeout(processTimer);
processTimer = undefined;
2015-05-04 01:06:35 +12:00
}
});
return {
addMany: addMany,
addIFrames: addIFrames,
addNodeList: addNodeList,
process: process
};
})();
2014-10-18 08:01:09 +13:00
/******************************************************************************/
2014-10-18 08:01:09 +13:00
/******************************************************************************/
var hasInlineScript = function(nodeList, summary) {
2014-10-18 08:01:09 +13:00
var i = 0;
var node, text;
while ( (node = nodeList.item(i++)) ) {
if ( node.nodeType !== 1 ) {
continue;
}
if ( node.localName === 'script' ) {
2014-10-18 08:01:09 +13:00
text = node.textContent.trim();
if ( text === '' ) {
continue;
2014-10-18 08:01:09 +13:00
}
summary.inlineScript = true;
2014-10-18 08:01:09 +13:00
break;
}
if ( node.localName === 'a' && node.href.lastIndexOf('javascript', 0) === 0 ) {
summary.inlineScript = true;
2014-10-18 08:01:09 +13:00
break;
}
}
if ( summary.inlineScript ) {
summary.mustReport = true;
}
2014-10-18 08:01:09 +13:00
};
var nodeListsAddedHandler = function(nodeLists) {
var i = nodeLists.length;
if ( i === 0 ) {
return;
}
2014-10-18 08:01:09 +13:00
var summary = {
what: 'contentScriptSummary',
locationURL: window.location.href,
inlineScript: false,
2014-10-18 08:01:09 +13:00
mustReport: false
};
while ( i-- ) {
if ( summary.inlineScript === false ) {
hasInlineScript(nodeLists[i], summary);
}
collapser.addNodeList(nodeLists[i]);
2014-10-18 08:01:09 +13:00
}
if ( summary.mustReport ) {
vAPI.messaging.send('contentscript.js', summary);
2014-10-18 08:01:09 +13:00
}
collapser.process();
2014-10-18 08:01:09 +13:00
};
/******************************************************************************/
/******************************************************************************/
// Executed only once.
2014-10-18 08:01:09 +13:00
(function() {
var summary = {
what: 'contentScriptSummary',
locationURL: window.location.href,
inlineScript: false,
mustReport: true
2014-10-18 08:01:09 +13:00
};
// https://github.com/gorhill/httpswitchboard/issues/25
// &
// Looks for inline javascript also in at least one a[href] element.
// https://github.com/gorhill/httpswitchboard/issues/131
hasInlineScript(document.querySelectorAll('a[href^="javascript:"],script'), summary);
2014-10-18 08:01:09 +13:00
//console.debug('contentscript.js > firstObservationHandler(): found %d script tags in "%s"', Object.keys(summary.scriptSources).length, window.location.href);
2014-10-18 08:01:09 +13:00
vAPI.messaging.send('contentscript.js', summary);
collapser.addMany(document.querySelectorAll('img'));
collapser.addIFrames(document.querySelectorAll('iframe'));
collapser.process();
2014-10-18 08:01:09 +13:00
})();
/******************************************************************************/
2014-10-18 08:01:09 +13:00
/******************************************************************************/
// Observe changes in the DOM
// Added node lists will be cumulated here before being processed
(function() {
// This fixes http://acid3.acidtests.org/
if ( !document.body ) { return; }
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) {
var iMutation = mutations.length,
nodeList;
while ( iMutation-- ) {
nodeList = mutations[iMutation].addedNodes;
if ( nodeList.length !== 0 ) {
addedNodeLists.push(nodeList);
}
2014-10-18 08:01:09 +13:00
}
if ( addedNodeListsTimer === null ) {
addedNodeListsTimer = vAPI.setTimeout(treeMutationObservedHandler, 47);
}
};
2014-10-18 08:01:09 +13:00
// https://github.com/gorhill/httpswitchboard/issues/176
var treeObserver = new MutationObserver(treeMutationObservedHandlerAsync);
treeObserver.observe(document.body, {
2014-10-18 08:01:09 +13:00
childList: true,
subtree: true
});
vAPI.shutdown.add(function() {
if ( addedNodeListsTimer !== null ) {
clearTimeout(addedNodeListsTimer);
addedNodeListsTimer = null;
}
if ( treeObserver !== null ) {
treeObserver.disconnect();
treeObserver = null;
}
addedNodeLists = [];
});
})();
2014-10-18 08:01:09 +13:00
/******************************************************************************/
2014-10-18 08:01:09 +13:00
/******************************************************************************/
// Executed only once.
2017-12-02 06:34:29 +13:00
// https://github.com/gorhill/uMatrix/issues/232
// Force `display` property, Firefox is still affected by the issue.
(function() {
var noscripts = document.querySelectorAll('noscript');
if ( noscripts.length === 0 ) { return; }
var redirectTimer,
2017-12-07 00:29:21 +13:00
reMetaContent = /^\s*(\d+)\s*;\s*url=(['"]?)(https?:\/\/[^'"]+)\2/;
var autoRefresh = function(root) {
var meta = root.querySelector('meta[http-equiv="refresh"][content]');
if ( meta === null ) { return; }
var match = reMetaContent.exec(meta.getAttribute('content'));
if ( match === null || match[3].trim() === '' ) { return; }
redirectTimer = setTimeout(
function() {
location.assign(match[3]);
},
parseInt(match[1], 10) * 1000 + 1
);
meta.parentNode.removeChild(meta);
};
var renderNoscriptTags = function(response) {
if ( response !== true ) { return; }
2017-12-02 11:16:29 +13:00
var parser = new DOMParser();
var doc, parent, span;
for ( var noscript of noscripts ) {
parent = noscript.parentNode;
if ( parent === null ) { continue; }
2017-12-02 11:16:29 +13:00
doc = parser.parseFromString(
'<span>' + noscript.textContent + '</span>',
'text/html'
);
span = document.adoptNode(doc.querySelector('span'));
2017-12-02 06:34:29 +13:00
span.style.setProperty('display', 'inline', 'important');
if ( redirectTimer === undefined ) {
autoRefresh(span);
}
parent.replaceChild(span, noscript);
}
};
vAPI.messaging.send(
'contentscript.js',
{ what: 'mustRenderNoscriptTags?' },
renderNoscriptTags
);
})();
/******************************************************************************/
/******************************************************************************/
vAPI.messaging.send(
'contentscript.js',
{ what: 'shutdown?' },
function(response) {
if ( response === true ) {
vAPI.shutdown.exec();
}
}
);
2015-04-12 09:15:57 +12:00
/******************************************************************************/
/******************************************************************************/
})();