From 0e77cbd15d1617d21dfb178958e230f2fc2ead78 Mon Sep 17 00:00:00 2001 From: gorhill Date: Sat, 2 May 2015 11:17:17 -0400 Subject: [PATCH] this fixes #45 + revised color-blind mode + support shutdown of content script --- platform/chromium/manifest.json | 6 +- src/_locales/en/messages.json | 4 + src/css/popup.css | 34 +-- src/img/permanent-black-small-cb.png | Bin 217 -> 159 bytes src/img/permanent-white-small-cb.png | Bin 230 -> 151 bytes src/js/background.js | 1 + src/js/contentscript-end.js | 380 ++++++++++++++++++++------- src/js/messaging.js | 68 ++++- src/settings.html | 4 + 9 files changed, 368 insertions(+), 129 deletions(-) diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json index 67c6942..53ea428 100644 --- a/platform/chromium/manifest.json +++ b/platform/chromium/manifest.json @@ -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", diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index fc62a15..de3686e 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -299,6 +299,10 @@ "message": "Opacity", "description": "English: Opacity" }, + "settingsCollapseBlocked" : { + "message": "Collapse placeholder of blocked elements", + "description": "English: Collapse placeholder of blocked elements" + }, "privacyPageTitle" : { diff --git a/src/css/popup.css b/src/css/popup.css index 90c9b51..6b3d4c3 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -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; } diff --git a/src/img/permanent-black-small-cb.png b/src/img/permanent-black-small-cb.png index 48f5361d648a8497075e5c9bcd2c2eaaf6d436dc..de45e78291bd89b0b318f5222f92592e6468a474 100644 GIT binary patch delta 130 zcmV-|0Db@20iOYoBztB_L_t(2&+X7L5r8ldMA4sEh=y}Ofdf?FfQgxyK>`dC5VI7N zkYui)zy{7 delta 202 zcmbQv_>6IaN 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 + } } - var scripts = document.querySelectorAll('noscript'); - var i = scripts.length; - var realNoscript, fakeNoscript; - while ( i-- ) { - realNoscript = scripts[i]; - fakeNoscript = document.createElement('div'); - fakeNoscript.innerHTML = '\n' + realNoscript.textContent; - realNoscript.parentNode.replaceChild(fakeNoscript, realNoscript); + catch (e) { } -}; - -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'); - } -}; +// 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; + } -// 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 ) { + // 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: 'contentScriptHasLocalStorage', - url: window.location.href - }, localStorageHandler); - } + what: 'evaluateURLs', + requests: newRequests + }, onProcessed); + newRequests = []; + }; - // TODO: indexedDB - if ( window.indexedDB && !!window.indexedDB.webkitGetDatabaseNames ) { - // var db = window.indexedDB.webkitGetDatabaseNames().onsuccess = function(sender) { - // console.debug('webkitGetDatabaseNames(): result=%o', sender.target.result); - // }; - } + var process = function(delay) { + if ( newRequests.length === 0 ) { + return; + } + if ( delay === 0 ) { + clearTimeout(timer); + send(); + } else if ( timer === null ) { + timer = setTimeout(send, delay || 50); + } + }; - // 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) { -} + 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')); + } + } + }; -(function() { + // 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,60 +401,86 @@ 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); + } + }; + + // This fixes http://acid3.acidtests.org/ + if ( document.body ) { + return; } - // 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 ) { // 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(); + } +}); /******************************************************************************/ /******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index 46e0209..2d80f63 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -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; } diff --git a/src/settings.html b/src/settings.html index f504d25..7e079df 100644 --- a/src/settings.html +++ b/src/settings.html @@ -57,12 +57,16 @@ ul > li {

  • + + +