1
0
Fork 0
mirror of https://github.com/gorhill/uMatrix.git synced 2024-05-19 19:53:19 +12:00

this fixes #45 + revised color-blind mode + support shutdown of content script

This commit is contained in:
gorhill 2015-05-02 11:17:17 -04:00
parent 1583a3edc6
commit 0e77cbd15d
9 changed files with 381 additions and 142 deletions

View file

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "µMatrix",
"short_name": "µMatrix",
"name": "uMatrix",
"short_name": "uMatrix",
"version": "0.8.1.4",
"description": "__MSG_extShortDesc__",
"icons": {
@ -12,7 +12,7 @@
"default_icon": {
"19": "img/browsericons/icon19-19.png"
},
"default_title": "µMatrix",
"default_title": "uMatrix",
"default_popup": "popup.html"
},
"author": "Raymond Hill",

View file

@ -299,6 +299,10 @@
"message": "Opacity",
"description": "English: Opacity"
},
"settingsCollapseBlocked" : {
"message": "Collapse placeholder of blocked elements",
"description": "English: Collapse placeholder of blocked elements"
},
"privacyPageTitle" : {

View file

@ -462,22 +462,22 @@ body.powerOff .matrix .g4Meta.g4Collapsed ~ .matRow.ro {
/* Cell coloring for color blind-friendly (hopefully) */
body.colorblind .t81 {
color: white;
background-color: #000;
background-color: rgb(0, 19, 110);
}
body.colorblind .t82 {
border-color: #aaa;
border-color: rgb(255, 194, 57);
color: black;
background-color: #fff;
background-color: rgb(255, 194, 57);
}
body.colorblind .t1 {
border-color: #333;
color: white;
background-color: #666;
border-color: rgba(0, 19, 110, 0.3);
color: black;
background-color: rgba(0, 19, 110, 0.2);
}
body.colorblind .t2 {
border-color: #aaa;
border-color: rgba(255, 194, 57, 0.3);
color: black;
background-color: #ccc;
background-color: rgba(255, 194, 57, 0.2);
}
body.colorblind .matCell.p81 {
background-image: url('../img/permanent-black-small-cb.png');
@ -533,18 +533,15 @@ body.powerOff #whitelist, body.powerOff #blacklist {
background-color: #080;
opacity: 0.25;
}
body.colorblind .rw .matCell.t1 #whitelist:hover {
background-color: #fff;
body.colorblind .rw .matCell.t1 #whitelist:hover,
body.colorblind .rw .matCell.t2 #whitelist:hover {
background-color: rgb(255, 194, 57);
opacity: 0.6;
}
.rw .matCell.t2 #whitelist:hover {
background-color: #080;
opacity: 0.25;
}
body.colorblind .rw .matCell.t2 #whitelist:hover {
background-color: #fff;
opacity: 0.6;
}
.matCell.t81 #whitelist:hover {
background-color: transparent;
}
@ -555,18 +552,15 @@ body.colorblind .rw .matCell.t2 #whitelist:hover {
background-color: #c00;
opacity: 0.25;
}
body.colorblind .rw .matCell.t1 #blacklist:hover {
background-color: #000;
body.colorblind .rw .matCell.t1 #blacklist:hover,
body.colorblind .rw .matCell.t2 #blacklist:hover {
background-color: rgb(0, 19, 110);
opacity: 0.4;
}
.rw .matCell.t2 #blacklist:hover {
background-color: #c00;
opacity: 0.25;
}
body.colorblind .rw .matCell.t2 #blacklist:hover {
background-color: #000;
opacity: 0.4;
}
.matCell.t81 #blacklist:hover {
background-color: transparent;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 B

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 151 B

View file

@ -53,6 +53,7 @@ return {
autoUpdate: false,
clearBrowserCache: true,
clearBrowserCacheAfter: 60,
collapseBlocked: false,
colorBlindFriendly: false,
deleteCookies: false,
deleteUnusedSessionCookies: false,

View file

@ -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,88 +63,243 @@ 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.
// Unrendered noscript (because CSP) workaround
// Executed once.
(function() {
var checkScriptBlacklistedHandler = function(response) {
if ( !response.scriptBlacklisted ) {
return;
}
var scripts = document.querySelectorAll('noscript');
var i = scripts.length;
var realNoscript, fakeNoscript;
while ( i-- ) {
realNoscript = scripts[i];
fakeNoscript = document.createElement('div');
fakeNoscript.innerHTML = '<!-- uMatrix NOSCRIPT tag replacement: see <https://github.com/gorhill/httpswitchboard/issues/177> -->\n' + realNoscript.textContent;
realNoscript.parentNode.replaceChild(fakeNoscript, realNoscript);
}
};
/******************************************************************************/
/*------------[ Unrendered Noscript (because CSP) Workaround ]----------------*/
var checkScriptBlacklistedHandler = function(response) {
if ( !response.scriptBlacklisted ) {
return;
}
var scripts = document.querySelectorAll('noscript');
var i = scripts.length;
var realNoscript, fakeNoscript;
while ( i-- ) {
realNoscript = scripts[i];
fakeNoscript = document.createElement('div');
fakeNoscript.innerHTML = '<!-- uMatrix NOSCRIPT tag replacement: see <https://github.com/gorhill/httpswitchboard/issues/177> -->\n' + realNoscript.textContent;
realNoscript.parentNode.replaceChild(fakeNoscript, realNoscript);
}
};
localMessager.send({
what: 'checkScriptBlacklisted',
url: window.location.href
}, checkScriptBlacklistedHandler);
/******************************************************************************/
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 ) {
localMessager.send({
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
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
}
}
catch (e) {
}
/******************************************************************************/
localMessager.send({
what: 'checkScriptBlacklisted',
url: window.location.href
}, checkScriptBlacklistedHandler);
})();
/******************************************************************************/
/******************************************************************************/
(function() {
// Executed only once.
(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 ) {
localMessager.send({
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
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
}
}
catch (e) {
}
})();
/******************************************************************************/
/******************************************************************************/
// 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.tagName !== 'string' ) {
if ( typeof node.localName !== '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.indexOf('uMatrix-') === 0 ) {
if ( typeof node.id === 'string' && node.id.lastIndexOf('uMatrix-', 0) === 0 ) {
break;
}
text = node.textContent.trim();
@ -179,14 +334,14 @@ var nodesAddedHandler = function(nodeList, summary) {
}
break;
case 'A':
if ( node.href.indexOf('javascript:') === 0 ) {
case 'a':
if ( node.href.lastIndexOf('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,13 +376,18 @@ 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 = {
@ -241,62 +401,88 @@ 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() {
nodeListsAddedHandler(addedNodeLists);
addedNodeListsTimer = null;
addedNodeLists = [];
};
(function() {
var addedNodeLists = [];
var addedNodeListsTimer = null;
// 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-- ) {
nodeList = mutations[iMutation].addedNodes;
if ( nodeList.length !== 0 ) {
addedNodeLists.push(nodeList);
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;
var nodeList;
while ( iMutation-- ) {
nodeList = mutations[iMutation].addedNodes;
if ( nodeList.length !== 0 ) {
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 ) {
addedNodeListsTimer = setTimeout(treeMutationObservedHandler, 250);
}
};
// 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 ) {
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();
}
});
/******************************************************************************/
/******************************************************************************/
})();

View file

@ -446,6 +446,45 @@ var contentScriptLocalStorageHandler = function(tabId, pageURL) {
/******************************************************************************/
// Evaluate many URLs against the matrix.
var evaluateURLs = function(tabId, requests) {
if ( µm.userSettings.collapseBlocked === false ) {
return requests;
}
// Create evaluation context
var tabContext = µm.tabContextManager.lookup(tabId);
if ( tabContext === null ) {
return requests;
}
var rootHostname = tabContext.rootHostname;
//console.debug('messaging.js/contentscript-end.js: processing %d requests', requests.length);
var µmuri = µm.URI;
var typeMap = tagNameToRequestTypeMap;
var request;
var i = requests.length;
while ( i-- ) {
request = requests[i];
request.collapse = µm.mustBlock(
rootHostname,
µmuri.hostnameFromURI(request.url),
typeMap[request.tagName]
);
}
return requests;
};
var tagNameToRequestTypeMap = {
'iframe': 'sub_frame',
'img': 'image'
};
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
@ -453,20 +492,12 @@ var onMessage = function(request, sender, callback) {
break;
}
var tabId = sender.tab.id;
var tabId = sender && sender.tab ? sender.tab.id || 0 : 0;
// Sync
var response;
switch ( request.what ) {
case 'contentScriptHasLocalStorage':
response = contentScriptLocalStorageHandler(tabId, request.url);
break;
case 'contentScriptSummary':
contentScriptSummaryHandler(tabId, request);
break;
case 'checkScriptBlacklisted':
response = {
scriptBlacklisted: µm.mustBlock(
@ -477,12 +508,31 @@ var onMessage = function(request, sender, callback) {
};
break;
case 'contentScriptHasLocalStorage':
response = contentScriptLocalStorageHandler(tabId, request.url);
break;
case 'contentScriptSummary':
contentScriptSummaryHandler(tabId, request);
break;
case 'evaluateURLs':
response = evaluateURLs(tabId, request.requests);
break;
case 'getUserAgentReplaceStr':
response = µm.tMatrix.evaluateSwitchZ('ua-spoof', request.hostname) ?
µm.userAgentReplaceStr :
undefined;
break;
case 'shutdown?':
var tabContext = µm.tabContextManager.lookup(tabId);
if ( tabContext !== null ) {
response = µm.tMatrix.evaluateSwitchZ('matrix-off', tabContext.rootHostname);
}
break;
default:
return vAPI.messaging.UNHANDLED;
}

View file

@ -57,12 +57,16 @@ ul > li {
<h2 data-i18n="settingsMatrixConvenienceHeader"></h2>
<ul>
<li>
<input id="collapseBlocked" type="checkbox" data-range="bool">
<label data-i18n="settingsCollapseBlocked" for="collapseBlocked"></label>
<!-- <li>
<span data-i18n="settingsMatrixAutoReloadPrompt"></span> <select id="smart-auto-reload">
<option value="none" data-i18n="settingsMatrixAutoReloadNone">
<option value="current" data-i18n="settingsMatrixAutoReloadCurrent">
<option value="all" data-i18n="settingsMatrixAutoReloadAll">
</select> <button class="whatisthis"></button>
<div class="whatisthis-expandable para" data-i18n="settingsMatrixAutoReloadInfo"></div>
-->
</ul>