diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 001e08567..db59d5be4 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -207,22 +207,14 @@ "message":"Änderungen anwenden", "description":"English: Apply changes" }, - "logBlockedRequestsPrompt":{ + "logNetRequestsPrompt":{ "message":"Protokollieren von blockierten Elementen aktivieren", - "description":"English: Enable the logging of blocked requests" + "description":"English: Enable the logging of network requests" }, - "logBlockedRequestsHelp":{ + "logNetRequestsHelp":{ "message":"Du kannst dir die Details von blockierten Anfragen anschauen, wenn du diese Option aktivierst. Das Protokollieren von blockierten Anfragen erhöht den Speicherbedarf von µBlock<\/i>. Da dieses Feature von vielen Benutzern wohl nicht genutzt werden wird, ist es standardmäßig deaktiviert.", "description":"English: see _locales\/en\/messages.log" }, - "logAllowedRequestsPrompt":{ - "message":"Protokollieren von nicht-blockierten Elementen aktivieren", - "description":"English: Enable the logging of non-blocked requests" - }, - "logAllowedRequestsHelp":{ - "message":"Du kannst dir die Details von nicht-blockierten Anfragen anschauen, wenn du diese Option aktivierst. Das Protokollieren von nicht-blockierten Anfragen erhöht den Speicherbedarf von µBlock. Da dieses Feature von vielen Benutzern wohl nicht genutzt werden wird, ist es standardmäßig deaktiviert.", - "description":"English: see _locales\/en\/messages.log" - }, "logBlockedRequestsHeader":{ "message":"Blockierte Anfragen", "description":"English: Blocked requests" diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 9b4dd6e34..6c75c2817 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -207,20 +207,12 @@ "message":"Apply changes", "description":"English: Apply changes" }, - "logBlockedRequestsPrompt":{ - "message":"Enable the logging of blocked requests", - "description":"English: Enable the logging of blocked requests" + "logNetRequestsPrompt":{ + "message":"Enable the logging of network requests", + "description":"English: Enable the logging of network requests" }, - "logBlockedRequestsHelp":{ - "message":"You can inspect the details of blocked requests if you wish by enabling this option. The logging of blocked requests increases the memory footprint of µBlock. Since many users will never use this feature, it is disabled by default.", - "description":"English: see _locales\/en\/messages.log" - }, - "logAllowedRequestsPrompt":{ - "message":"Enable the logging of non-blocked requests", - "description":"English: Enable the logging of non-blocked requests" - }, - "logAllowedRequestsHelp":{ - "message":"You can inspect the details of non-blocked requests if you wish by enabling this option. The logging of non-blocked requests increases the memory footprint of µBlock. Since many users will never use this feature, it is disabled by default.", + "logNetRequestsHelp":{ + "message":"You can inspect the details of network requests if you wish by enabling this option. The logging of network requests increases the memory footprint of µBlock. Since many users will never use this feature, it is disabled by default.", "description":"English: see _locales\/en\/messages.log" }, "logBlockedRequestsHeader":{ diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 3e1e30cfc..19e030c80 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -207,20 +207,12 @@ "message":"Appliquer", "description":"English: Apply changes" }, - "logBlockedRequestsPrompt":{ - "message":"Activer la journalisation des requêtes bloquées", - "description":"English: Enable the logging of blocked requests" + "logNetRequestsPrompt":{ + "message":"Activer la journalisation des requêtes", + "description":"English: Enable the logging of network requests" }, - "logBlockedRequestsHelp":{ - "message":"Vous pouvez inspecter les détails des requêtes bloquées en activant cette option. La journalisation des requêtes bloquées augmente l'empreinte mémoire utilisée par µBlock. Puisque que bon nombre d'utilisateurs n'utiliseront jamais cette fonctionnalité, elle est désactivée par défaut.", - "description":"English: see _locales\/en\/messages.log" - }, - "logAllowedRequestsPrompt":{ - "message":"Activer la journalisation des requêtes autorisées", - "description":"English: Enable the logging of non-blocked requests" - }, - "logAllowedRequestsHelp":{ - "message":"Vous pouvez inspecter les détails des requêtes autorisées en activant cette option. La journalisation des requêtes autorisées augmente l'empreinte mémoire utilisée par µBlock. Puisque que bon nombre d'utilisateurs n'utiliseront jamais cette fonctionnalité, elle est désactivée par défaut.", + "logNetRequestsHelp":{ + "message":"Vous pouvez inspecter les détails des requêtes en activant cette option. La journalisation des requêtes augmente l'empreinte mémoire utilisée par µBlock. Puisque que bon nombre d'utilisateurs n'utiliseront jamais cette fonctionnalité, elle est désactivée par défaut.", "description":"English: see _locales\/en\/messages.log" }, "logBlockedRequestsHeader":{ diff --git a/background.html b/background.html index 503b1fc11..680190082 100644 --- a/background.html +++ b/background.html @@ -14,8 +14,8 @@ - - + + diff --git a/js/async.js b/js/async.js index 66241e5a8..ab63545a3 100644 --- a/js/async.js +++ b/js/async.js @@ -27,6 +27,8 @@ µBlock.asyncJobs = (function() { +/******************************************************************************/ + var processJobs = function() { asyncJobManager.process(); }; @@ -45,6 +47,8 @@ AsyncJobEntry.prototype.destroy = function() { this.callback = null; }; +/******************************************************************************/ + var AsyncJobManager = function() { this.timeResolution = 200; this.jobs = {}; @@ -54,7 +58,15 @@ var AsyncJobManager = function() { this.timerWhen = Number.MAX_VALUE; }; +/******************************************************************************/ + AsyncJobManager.prototype.restartTimer = function() { + // TODO: Another way to do this is to extract the keys, sort the keys + // in chronological order, than pick the first entry to get the next + // time at which we want a time event to fire. Completely unsure the + // overhead of extracting keys/sorting is less than what is below. + // I could also keep the keys ordered, and use binary search when adding + // a new job. var when = Number.MAX_VALUE; var jobs = this.jobs, job; for ( var jobName in jobs ) { @@ -68,6 +80,10 @@ AsyncJobManager.prototype.restartTimer = function() { // Quantize time value when = Math.floor((when + this.timeResolution - 1) / this.timeResolution) * this.timeResolution; + // TODO: Maybe use chrome.alarms() API when the next job is at more than + // one minute in the future... From reading about it, chrome.alarms() is + // smarter in that it will fire the event only when the browser is not + // too busy. (through XAL to abstract API specificities) if ( when < this.timerWhen ) { clearTimeout(this.timerId); this.timerWhen = when; @@ -75,6 +91,8 @@ AsyncJobManager.prototype.restartTimer = function() { } }; +/******************************************************************************/ + AsyncJobManager.prototype.add = function(name, data, callback, delay, recurrent) { var job = this.jobs[name]; if ( !job ) { @@ -94,6 +112,22 @@ AsyncJobManager.prototype.add = function(name, data, callback, delay, recurrent) this.restartTimer(); }; +/******************************************************************************/ + +AsyncJobManager.prototype.remove = function(jobName) { + if ( this.jobs.hasOwnProperty(jobName) === false ) { + return; + } + var job = this.jobs[jobName]; + delete this.jobs[jobName]; + job.destroy(); + this.jobCount--; + this.jobJunkyard.push(job); + this.restartTimer(); +}; + +/******************************************************************************/ + AsyncJobManager.prototype.process = function() { this.timerId = null; this.timerWhen = Number.MAX_VALUE; @@ -120,9 +154,13 @@ AsyncJobManager.prototype.process = function() { this.restartTimer(); }; +/******************************************************************************/ + // Only one instance var asyncJobManager = new AsyncJobManager(); +/******************************************************************************/ + // Publish return asyncJobManager; @@ -133,12 +171,12 @@ return asyncJobManager; // Update visual of extension icon. // A time out is used to coalesce adjacent requests to update badge. -µBlock.updateBadgeAsync = function(tabId) { - if ( tabId < 0 ) { - return; - } - var µb = this; - var updateBadge = function() { +µBlock.updateBadgeAsync = (function(){ + var µb = µBlock; + + // Cache callback definition, it was a bad idea to define this one inside + // updateBadgeAsync + var updateBadge = function(tabId) { var pageStore = µb.pageStoreFromTabId(tabId); if ( pageStore ) { pageStore.updateBadge(); @@ -150,5 +188,13 @@ return asyncJobManager; '' ); }; - this.asyncJobs.add('updateBadge-' + tabId, tabId, updateBadge, 250); -}; + + var updateBadgeAsync = function(tabId) { + if ( tabId < 0 ) { + return; + } + µb.asyncJobs.add('updateBadge-' + tabId, tabId, updateBadge, 250); + }; + + return updateBadgeAsync; +})(); \ No newline at end of file diff --git a/js/background.js b/js/background.js index 2c2ef1a7b..4fd232448 100644 --- a/js/background.js +++ b/js/background.js @@ -41,8 +41,7 @@ return { autoUpdate: true, collapseBlocked: true, externalLists: '', - logBlockedRequests: false, - logAllowedRequests: false, + logRequests: false, parseAllABPHideFilters: true, showIconBadge: true }, diff --git a/js/contentscript-end.js b/js/contentscript-end.js index 9c2cdb1c2..e6d254ec3 100644 --- a/js/contentscript-end.js +++ b/js/contentscript-end.js @@ -56,16 +56,7 @@ var uBlockMessaging = (function(name){ }; var start = function(name) { - port = chrome.runtime.connect({ - name: name + - '/' + - String.fromCharCode( - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0 - ) - }); + port = chrome.runtime.connect({ name: name }); port.onMessage.addListener(onPortMessage); // https://github.com/gorhill/uBlock/issues/193 @@ -111,7 +102,7 @@ var uBlockMessaging = (function(name){ var flushCallbacks = function() { var callback; - for ( id in requestIdToCallbackMap ) { + for ( var id in requestIdToCallbackMap ) { if ( requestIdToCallbackMap.hasOwnProperty(id) === false ) { continue; } @@ -527,63 +518,11 @@ var uBlockMessaging = (function(name){ /******************************************************************************/ /******************************************************************************/ -// https://github.com/gorhill/uBlock/issues/7 +// Permanent (function() { var messaging = uBlockMessaging; - var blockableElements = { - 'embed': 'src', - 'iframe': 'src', - 'img': 'src', - 'object': 'data' - }; - - // First pass - messaging.ask({ what: 'blockedRequests' }, function(details) { - var elems = document.querySelectorAll('embed,iframe,img,object'); - var blockedRequests = details.blockedRequests; - var collapse = details.collapse; - var i = elems.length; - var elem, tagName, prop, src; - var selectors = []; - while ( i-- ) { - elem = elems[i]; - tagName = elem.tagName.toLowerCase(); - prop = blockableElements[tagName]; - if ( prop === undefined ) { - continue; - } - src = elem[prop]; - if ( typeof src !== 'string' || src === '' ) { - continue; - } - if ( blockedRequests[src] === undefined ) { - continue; - } - // If `!important` is not there, going back using history will - // likely cause the hidden element to re-appear. - if ( collapse ) { - if ( elem.parentNode ) { - elem.parentNode.removeChild(elem); - } else { - elem.style.setProperty('display', 'none', 'important'); - } - } else { - elem.style.setProperty('visibility', 'hidden', 'important'); - } - selectors.push(tagName + '[' + prop + '="' + src + '"]'); - } - if ( selectors.length !== 0 ) { - messaging.tell({ - what: 'injectedSelectors', - type: 'net', - hostname: window.location.hostname, - selectors: selectors - }); - } - }); - // Listeners to mop up whatever is otherwise missed: // - Future requests not blocked yet // - Elements dynamically added to the page @@ -612,11 +551,15 @@ var uBlockMessaging = (function(name){ if ( typeof src !== 'string' || src === '' ) { return; } + if ( src.slice(0, 4) !== 'http' ) { + return; + } + // https://github.com/gorhill/uBlock/issues/174 // Do not remove fragment from src URL var onAnswerReceived = function(details) { - if ( !details.blocked ) { + if ( typeof details !== 'object' || details === null ) { return; } // If `!important` is not there, going back using history will @@ -637,7 +580,15 @@ var uBlockMessaging = (function(name){ selectors: tagName + '[' + prop + '="' + src + '"]' }); }; - messaging.ask({ what: 'blockedRequest', url: src }, onAnswerReceived); + + var details = { + what: 'filterRequest', + tagName: tagName, + requestURL: src, + pageHostname: window.location.hostname, + pageURL: window.location.href + }; + messaging.ask(details, onAnswerReceived); }; var onResourceLoaded = function(ev) { @@ -655,3 +606,95 @@ var uBlockMessaging = (function(name){ })(); /******************************************************************************/ +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/7 + +// Executed only once + +(function() { + var messaging = uBlockMessaging; + + var readyProps = { + 'img': 'complete' + }; + var srcProps = { + 'embed': 'src', + 'iframe': 'src', + 'img': 'src', + 'object': 'data' + }; + var elements = []; + + var onAnswerReceived = function(details) { + if ( typeof details !== 'object' || details === null ) { + return; + } + var requests = details.requests; + var collapse = details.collapse; + var selectors = []; + var i = requests.length; + var request, elem; + while ( i-- ) { + request = requests[i]; + elem = elements[request.index]; + if ( collapse ) { + if ( elem.parentNode ) { + elem.parentNode.removeChild(elem); + } else { + elem.style.setProperty('display', 'none', 'important'); + } + } else { + elem.style.setProperty('visibility', 'hidden', 'important'); + } + selectors.push(request.tagName + '[' + srcProps[request.tagName] + '="' + request.url + '"]'); + } + if ( selectors.length !== 0 ) { + messaging.tell({ + what: 'injectedSelectors', + type: 'net', + hostname: window.location.hostname, + selectors: selectors + }); + } + }; + + var requests = []; + var tagNames = ['embed','iframe','img','object']; + var elementIndex = 0; + var tagName, elems, i, elem, prop, src; + while ( tagName = tagNames.pop() ) { + elems = document.getElementsByTagName(tagName); + i = elems.length; + while ( i-- ) { + elem = elems[i]; + prop = srcProps[tagName]; + if ( prop === undefined ) { + continue; + } + src = elem[prop]; + if ( typeof src !== 'string' || src === '' ) { + continue; + } + if ( src.slice(0, 4) !== 'http' ) { + continue; + } + requests.push({ + index: elementIndex, + tagName: tagName, + url: src + }); + elements[elementIndex] = elem; + elementIndex += 1; + } + } + var details = { + what: 'filterRequests', + pageURL: window.location.href, + pageHostname: window.location.hostname, + requests: requests + }; + messaging.ask(details, onAnswerReceived); +})(); + +/******************************************************************************/ diff --git a/js/contentscript-start.js b/js/contentscript-start.js index 627205100..2928d6202 100644 --- a/js/contentscript-start.js +++ b/js/contentscript-start.js @@ -66,16 +66,7 @@ var uBlockMessaging = (function(name){ }; var start = function(name) { - port = chrome.runtime.connect({ - name: name + - '/' + - String.fromCharCode( - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0 - ) - }); + port = chrome.runtime.connect({ name: name }); port.onMessage.addListener(onPortMessage); // https://github.com/gorhill/uBlock/issues/193 @@ -121,7 +112,7 @@ var uBlockMessaging = (function(name){ var flushCallbacks = function() { var callback; - for ( id in requestIdToCallbackMap ) { + for ( var id in requestIdToCallbackMap ) { if ( requestIdToCallbackMap.hasOwnProperty(id) === false ) { continue; } diff --git a/js/element-picker.js b/js/element-picker.js index dbb14b51c..5bc2e0323 100644 --- a/js/element-picker.js +++ b/js/element-picker.js @@ -143,16 +143,7 @@ var messaging = (function(name){ }; var start = function(name) { - port = chrome.runtime.connect({ - name: name + - '/' + - String.fromCharCode( - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0 - ) - }); + port = chrome.runtime.connect({ name: name }); port.onMessage.addListener(onPortMessage); // https://github.com/gorhill/uBlock/issues/193 @@ -198,7 +189,7 @@ var messaging = (function(name){ var flushCallbacks = function() { var callback; - for ( id in requestIdToCallbackMap ) { + for ( var id in requestIdToCallbackMap ) { if ( requestIdToCallbackMap.hasOwnProperty(id) === false ) { continue; } diff --git a/js/messaging-client.js b/js/messaging-client.js index a9313f0be..1236f69db 100644 --- a/js/messaging-client.js +++ b/js/messaging-client.js @@ -68,16 +68,7 @@ var messaging = (function(name){ }; var start = function(name) { - port = chrome.runtime.connect({ - name: name + - '/' + - String.fromCharCode( - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0, - Math.random() * 0x7FFF | 0 - ) - }); + port = chrome.runtime.connect({ name: name }); port.onMessage.addListener(onPortMessage); // https://github.com/gorhill/uBlock/issues/193 @@ -123,7 +114,7 @@ var messaging = (function(name){ var flushCallbacks = function() { var callback; - for ( id in requestIdToCallbackMap ) { + for ( var id in requestIdToCallbackMap ) { if ( requestIdToCallbackMap.hasOwnProperty(id) === false ) { continue; } diff --git a/js/messaging-handlers.js b/js/messaging-handlers.js index 89322bdd6..d7c688625 100644 --- a/js/messaging-handlers.js +++ b/js/messaging-handlers.js @@ -21,6 +21,7 @@ /* global chrome, µBlock, YaMD5 */ +/******************************************************************************/ /******************************************************************************/ (function() { @@ -40,8 +41,7 @@ var getStats = function(request) { pageAllowedRequestCount: 0, netFilteringSwitch: false, cosmeticFilteringSwitch: false, - logBlockedRequests: µb.userSettings.logBlockedRequests, - logAllowedRequests: µb.userSettings.logAllowedRequests + logRequests: µb.userSettings.logRequests }; var pageStore = µb.pageStoreFromTabId(request.tabId); if ( pageStore ) { @@ -95,6 +95,7 @@ var onMessage = function(request, sender, callback) { })(); +/******************************************************************************/ /******************************************************************************/ // contentscript-start.js @@ -136,6 +137,7 @@ var onMessage = function(request, sender, callback) { })(); +/******************************************************************************/ /******************************************************************************/ // contentscript-end.js @@ -144,9 +146,70 @@ var onMessage = function(request, sender, callback) { var µb = µBlock; -var onMessage = function(request, sender, callback) { +/******************************************************************************/ + +var tagNameToRequestTypeMap = { + 'embed': 'object', + 'iframe': 'sub_frame', + 'img': 'image', + 'object': 'object' +}; + +/******************************************************************************/ + +// Evaluate many requests + +var filterRequests = function(pageStore, details) { + details.pageDomain = µb.URI.domainFromHostname(details.pageHostname); + + var inRequests = details.requests; + var outRequests = []; + var request, result; + var i = inRequests.length; + while ( i-- ) { + request = inRequests[i]; + if ( tagNameToRequestTypeMap.hasOwnProperty(request.tagName) === false ) { + continue; + } + result = pageStore.filterRequest( + details, + tagNameToRequestTypeMap[request.tagName], + request.url + ); + if ( pageStore.boolFromResult(result) ) { + outRequests.push(request); + } + } + return { + collapse: µb.userSettings.collapseBlocked, + requests: outRequests + }; +}; + +/******************************************************************************/ + +// Evaluate a single request + +var filterRequest = function(pageStore, details) { + if ( tagNameToRequestTypeMap.hasOwnProperty(details.tagName) === false ) { + return; + } + details.pageDomain = µb.URI.domainFromHostname(details.pageHostname); + var result = pageStore.filterRequest( + details, + tagNameToRequestTypeMap[details.tagName], + details.requestURL + ); + if ( pageStore.boolFromResult(result) ) { + return { collapse: µb.userSettings.collapseBlocked }; + } +}; + +/******************************************************************************/ + +var onMessage = function(details, sender, callback) { // Async - switch ( request.what ) { + switch ( details.what ) { default: break; } @@ -159,34 +222,33 @@ var onMessage = function(request, sender, callback) { pageStore = µb.pageStoreFromTabId(sender.tab.id); } - switch ( request.what ) { + switch ( details.what ) { case 'retrieveGenericCosmeticSelectors': if ( pageStore && pageStore.getNetFilteringSwitch() ) { - response = µb.cosmeticFilteringEngine.retrieveGenericSelectors(request); + response = µb.cosmeticFilteringEngine.retrieveGenericSelectors(details); } break; case 'injectedSelectors': - µb.cosmeticFilteringEngine.addToSelectorCache(request); + µb.cosmeticFilteringEngine.addToSelectorCache(details); break; - case 'blockedRequests': - response = { - collapse: µb.userSettings.collapseBlocked, - blockedRequests: pageStore ? pageStore.blockedRequests : {} - }; + // Evaluate many requests + case 'filterRequests': + if ( pageStore && pageStore.getNetFilteringSwitch() ) { + response = filterRequests(pageStore, details); + } break; - // Check a single request - case 'blockedRequest': - response = { - collapse: µb.userSettings.collapseBlocked, - blocked: pageStore && pageStore.blockedRequests[request.url] - }; + // Evaluate a single request + case 'filterRequest': + if ( pageStore && pageStore.getNetFilteringSwitch() ) { + response = filterRequest(pageStore, details); + } break; default: - return µb.messaging.defaultHandler(request, sender, callback); + return µb.messaging.defaultHandler(details, sender, callback); } callback(response); @@ -196,6 +258,7 @@ var onMessage = function(request, sender, callback) { })(); +/******************************************************************************/ /******************************************************************************/ // element-picker.js @@ -238,6 +301,7 @@ var onMessage = function(request, sender, callback) { })(); +/******************************************************************************/ /******************************************************************************/ // 3p-filters.js @@ -309,6 +373,7 @@ var onMessage = function(request, sender, callback) { })(); +/******************************************************************************/ /******************************************************************************/ // 1p-filters.js @@ -345,6 +410,7 @@ var onMessage = function(request, sender, callback) { })(); +/******************************************************************************/ /******************************************************************************/ // whitelist.js @@ -384,6 +450,7 @@ var onMessage = function(request, sender, callback) { })(); +/******************************************************************************/ /******************************************************************************/ // stats.js @@ -400,46 +467,48 @@ var getPageDetails = function(µb, tabId) { if ( !pageStore ) { return r; } - var prepareRequests = function(requests, hasher) { + var prepareRequests = function(wantBlocked, hasher) { var µburi = µb.URI; + var dict = pageStore.netFilteringCache.fetchAll(); var r = []; - var details, pos, hostname, domain; - for ( var requestURL in requests ) { - if ( requests.hasOwnProperty(requestURL) === false ) { + var details, pos, result, hostname, domain; + for ( var url in dict ) { + if ( dict.hasOwnProperty(url) === false ) { continue; } - details = requests[requestURL]; + details = dict[url].data; if ( typeof details !== 'string' ) { continue; } - hasher.appendStr(requestURL); - hasher.appendStr(details); pos = details.indexOf('\t'); - hostname = µburi.hostnameFromURI(requestURL); - domain = µburi.domainFromHostname(hostname); - if ( domain === '' ) { - domain = hostname; + result = details.slice(pos + 1); + if ( wantBlocked !== pageStore.boolFromResult(result) ) { + continue; } + hasher.appendStr(url); + hasher.appendStr(details); + hostname = µburi.hostnameFromURI(url); + domain = µburi.domainFromHostname(hostname) || hostname; r.push({ type: details.slice(0, pos), domain: domain, - url: requestURL, - reason: details.slice(pos + 1) + url: url, + reason: result }); } return r; }; var hasher = new YaMD5(); - if ( µb.userSettings.logBlockedRequests ) { - r.blockedRequests = prepareRequests(pageStore.blockedRequests, hasher); - } - if ( µb.userSettings.logAllowedRequests ) { - r.allowedRequests = prepareRequests(pageStore.allowedRequests, hasher); + if ( µb.userSettings.logRequests ) { + r.blockedRequests = prepareRequests(true, hasher); + r.allowedRequests = prepareRequests(false, hasher); } r.hash = hasher.end(); return r; }; +/******************************************************************************/ + var onMessage = function(request, sender, callback) { var µb = µBlock; @@ -472,6 +541,7 @@ var onMessage = function(request, sender, callback) { })(); +/******************************************************************************/ /******************************************************************************/ // about.js diff --git a/js/messaging.js b/js/messaging.js index 4e34ec7e5..715fbc8ac 100644 --- a/js/messaging.js +++ b/js/messaging.js @@ -50,6 +50,7 @@ /******************************************************************************/ +var runtimeIdGenerator = 1; var nameToPortMap = {}; var nameToListenerMap = {}; var nullFunc = function(){}; @@ -58,7 +59,7 @@ var nullFunc = function(){}; var listenerNameFromPortName = function(portName) { var pos = portName.indexOf('/'); - if ( pos <= 0 ) { + if ( pos === -1 ) { return ''; } return portName.slice(0, pos); @@ -213,14 +214,13 @@ var onDisconnect = function(port) { var onConnect = function(port) { // We must have a port name. - if ( !port.name ) { - throw 'µBlock> messaging.js / onConnectHandler(): no port name!'; + if ( typeof port.name !== 'string' || port.name === '' ) { + console.error('µBlock> messaging.js / onConnectHandler(): no port name!'); + return; } - // Port should not already exist. - if ( nameToPortMap[port.name] ) { - throw 'µBlock> messaging.js / onConnectHandler(): port already exists!'; - } + // Ensure port name is unique + port.name += '/' + runtimeIdGenerator++; nameToPortMap[port.name] = port; port.onMessage.addListener(onMessage); diff --git a/js/net-filtering.js b/js/net-filtering.js index 0d2b927d5..33fdf95e9 100644 --- a/js/net-filtering.js +++ b/js/net-filtering.js @@ -28,6 +28,8 @@ /******************************************************************************/ +var µb = µBlock; + // fedcba9876543210 // ||| | | | // ||| | | | @@ -1023,7 +1025,7 @@ FilterParser.prototype.parseOptParty = function(not) { /******************************************************************************/ FilterParser.prototype.parseOptHostnames = function(raw) { - var µburi = µBlock.URI; + var µburi = µb.URI; var hostnames = raw.split('|'); var hostname, not, domain; for ( var i = 0; i < hostnames.length; i++ ) { @@ -1155,8 +1157,8 @@ FilterParser.prototype.parse = function(s) { var FilterContainer = function() { this.reAnyToken = /[%0-9a-z]+/g; this.buckets = new Array(8); - this.blockedAnyPartyHostnames = new µBlock.LiquidDict(); - this.blocked3rdPartyHostnames = new µBlock.LiquidDict(); + this.blockedAnyPartyHostnames = new µb.LiquidDict(); + this.blocked3rdPartyHostnames = new µb.LiquidDict(); this.filterParser = new FilterParser(); this.noDomainBits = this.toDomainBits(noDomainName); this.reset(); @@ -1633,7 +1635,7 @@ FilterContainer.prototype.matchAnyPartyHostname = function(requestHostname) { return '||' + requestHostname + '^'; } // Check parent hostnames if quick test failed - var hostnames = µBlock.URI.parentHostnamesFromHostname(requestHostname); + var hostnames = µb.URI.parentHostnamesFromHostname(requestHostname); for ( var i = 0, n = hostnames.length; i < n; i++ ) { if ( this.blockedAnyPartyHostnames.test(hostnames[i]) ) { return '||' + hostnames[i] + '^'; @@ -1658,7 +1660,7 @@ FilterContainer.prototype.match3rdPartyHostname = function(requestHostname) { return '||' + requestHostname + '^$third-party'; } // Check parent hostnames if quick test failed - var hostnames = µBlock.URI.parentHostnamesFromHostname(requestHostname); + var hostnames = µb.URI.parentHostnamesFromHostname(requestHostname); for ( var i = 0, n = hostnames.length; i < n; i++ ) { if ( this.blocked3rdPartyHostnames.test(hostnames[i]) ) { return '||' + hostnames[i] + '^$third-party'; @@ -1675,9 +1677,10 @@ FilterContainer.prototype.match3rdPartyHostname = function(requestHostname) { // Some type of requests are exceptional, they need custom handling, // not the generic handling. -FilterContainer.prototype.matchStringExactType = function(pageDetails, requestURL, requestType, requestHostname) { +FilterContainer.prototype.matchStringExactType = function(pageDetails, requestURL, requestType) { var url = requestURL.toLowerCase(); var pageDomain = pageDetails.pageDomain || ''; + var requestHostname = µb.URI.hostnameFromURI(requestURL); var party = requestHostname.slice(-pageDomain.length) === pageDomain ? FirstParty : ThirdParty; @@ -1710,7 +1713,7 @@ FilterContainer.prototype.matchStringExactType = function(pageDetails, requestUR buckets[7] = categories[this.makeCategoryKey(BlockOneParty | type | this.noDomainBits)]; bf = this.matchTokens(url); if ( bf === false ) { - return false; + return ''; } // Test against allow filters @@ -1728,7 +1731,7 @@ FilterContainer.prototype.matchStringExactType = function(pageDetails, requestUR /******************************************************************************/ -FilterContainer.prototype.matchString = function(pageDetails, requestURL, requestType, requestHostname) { +FilterContainer.prototype.matchString = function(pageDetails, requestURL, requestType) { // https://github.com/gorhill/httpswitchboard/issues/239 // Convert url to lower case: // `match-case` option not supported, but then, I saw only one @@ -1757,9 +1760,17 @@ FilterContainer.prototype.matchString = function(pageDetails, requestURL, reques // filter. var pageDomain = pageDetails.pageDomain || ''; - var party = requestHostname.slice(-pageDomain.length) === pageDomain ? - FirstParty : - ThirdParty; + var requestHostname = µb.URI.hostnameFromURI(requestURL); + + // Find out the relation between the page and request + var party = ThirdParty; + if ( requestHostname.slice(0 - pageDomain.length) === pageDomain ) { + // Be sure to not confuse 'example.com' with 'anotherexample.com' + var c = requestHostname.charAt(0 - pageDomain.length - 1); + if ( c === '' || c === '.' ) { + party = FirstParty; + } + } // This will be used by hostname-based filters pageHostname = pageDetails.pageHostname || ''; @@ -1784,7 +1795,7 @@ FilterContainer.prototype.matchString = function(pageDetails, requestURL, reques buckets[7] = categories[this.makeCategoryKey(BlockOneParty | Important | type | this.noDomainBits)]; var bf = this.matchTokens(url); if ( bf !== false ) { - return bf.toString(); + return bf.toString() + '$important'; } // Test hostname-based block filters @@ -1810,7 +1821,7 @@ FilterContainer.prototype.matchString = function(pageDetails, requestURL, reques // If there is no block filter, no need to test against allow filters if ( bf === false ) { - return false; + return ''; } // Test against allow filters diff --git a/js/pagestore.js b/js/pagestore.js index 1465f8431..eba4c27df 100644 --- a/js/pagestore.js +++ b/js/pagestore.js @@ -38,32 +38,180 @@ To create a log of net requests /******************************************************************************/ var µb = µBlock; -var frameStoreJunkyard = []; -var pageStoreJunkyard = []; + +/******************************************************************************/ +/******************************************************************************/ + +// To mitigate memory churning +var netFilteringResultCacheEntryJunkyard = []; +var netFilteringResultCacheEntryJunkyardMax = 200; /******************************************************************************/ -var frameStoreFactory = function(frameURL) { - var entry = frameStoreJunkyard.pop(); - if ( entry ) { - return entry.init(frameURL); - } - return new FrameStore(frameURL); +var NetFilteringResultCacheEntry = function(data) { + this.init(data); }; -var disposeFrameStores = function(map) { - for ( var k in map ) { - if ( map.hasOwnProperty(k) === false ) { +/******************************************************************************/ + +NetFilteringResultCacheEntry.prototype.init = function(data) { + this.data = data; + this.time = Date.now(); +}; + +/******************************************************************************/ + +NetFilteringResultCacheEntry.prototype.dispose = function() { + this.data = null; + if ( netFilteringResultCacheEntryJunkyard.length < netFilteringResultCacheEntryJunkyardMax ) { + netFilteringResultCacheEntryJunkyard.push(this); + } +}; + +/******************************************************************************/ + +NetFilteringResultCacheEntry.factory = function(data) { + var entry = netFilteringResultCacheEntryJunkyard.pop(); + if ( entry === undefined ) { + entry = new NetFilteringResultCacheEntry(data); + } else { + entry.init(data); + } + return entry; +}; + +/******************************************************************************/ +/******************************************************************************/ + +// To mitigate memory churning +var uidGenerator = 1; +var netFilteringCacheJunkyard = []; +var netFilteringCacheJunkyardMax = 10; + +/******************************************************************************/ + +var NetFilteringResultCache = function() { + this.init(); +}; + +/******************************************************************************/ + +NetFilteringResultCache.factory = function() { + var entry = netFilteringCacheJunkyard.pop(); + if ( entry === undefined ) { + entry = new NetFilteringResultCache(); + } else { + entry.init(); + } + return entry; +}; + +/******************************************************************************/ + +NetFilteringResultCache.prototype.init = function() { + this.uname = 'NetFilteringResultCache:' + uidGenerator++; + this.urls = {}; + this.count = 0; + this.shelfLife = 60 * 1000; +}; + +/******************************************************************************/ + +NetFilteringResultCache.prototype.dispose = function() { + for ( var key in this.urls ) { + if ( this.urls.hasOwnProperty(key) === false ) { continue; } - if ( frameStoreJunkyard.length > 50 ) { + this.urls[key].dispose(); + } + µBlock.asyncJobs.remove(this.uname); + this.uname = ''; + this.urls = {}; + this.count = 0; + if ( netFilteringCacheJunkyard.length < netFilteringCacheJunkyardMax ) { + netFilteringCacheJunkyard.push(this); + } + return null; +}; + +/******************************************************************************/ + +NetFilteringResultCache.prototype.add = function(url, data) { + var entry = this.urls[url]; + if ( entry !== undefined ) { + entry.data = data; + entry.time = Date.now(); + return; + } + this.urls[url] = NetFilteringResultCacheEntry.factory(data); + if ( this.count === 0 ) { + this.pruneAsync(); + } + this.count += 1; +}; + +/******************************************************************************/ + +NetFilteringResultCache.prototype.fetchAll = function() { + return this.urls; +}; + +/******************************************************************************/ + +NetFilteringResultCache.prototype.compareEntries = function(a, b) { + return this.urls[b].time - this.urls[a].time; +}; + +/******************************************************************************/ + +NetFilteringResultCache.prototype.prune = function() { + var keys = Object.keys(this.urls).sort(this.compareEntries.bind(this)); + var obsolete = Date.now() - this.shelfLife; + var key, entry; + var i = keys.length; + while ( i-- ) { + key = keys[i]; + entry = this.urls[key]; + if ( entry.time > obsolete ) { break; } - frameStoreJunkyard.push(map[k].dispose()); + entry.dispose(); + delete this.urls[key]; + } + this.count -= keys.length - i - 1; + if ( this.count > 0 ) { + this.pruneAsync(); } - return {}; }; +// https://www.youtube.com/watch?v=0vTBZzB_gfY + +/******************************************************************************/ + +NetFilteringResultCache.prototype.pruneAsync = function() { + µBlock.asyncJobs.add( + this.uname, + null, + this.prune.bind(this), + this.shelfLife + 120000, + false + ); +}; + +/******************************************************************************/ + +NetFilteringResultCache.prototype.lookup = function(url) { + var entry = this.urls[url]; + return entry !== undefined ? entry.data : undefined; +}; + +/******************************************************************************/ +/******************************************************************************/ + +// To mitigate memory churning +var frameStoreJunkyard = []; +var frameStoreJunkyardMax = 50; + /******************************************************************************/ var FrameStore = function(frameURL) { @@ -72,6 +220,18 @@ var FrameStore = function(frameURL) { /******************************************************************************/ +FrameStore.factory = function(frameURL) { + var entry = frameStoreJunkyard.pop(); + if ( entry === undefined ) { + entry = new FrameStore(frameURL); + } else { + entry.init(frameURL); + } + return entry; +}; + +/******************************************************************************/ + FrameStore.prototype.init = function(frameURL) { var µburi = µb.URI; this.pageHostname = µburi.hostnameFromURI(frameURL); @@ -83,18 +243,24 @@ FrameStore.prototype.init = function(frameURL) { FrameStore.prototype.dispose = function() { this.pageHostname = this.pageDomain = ''; - return this; + if ( frameStoreJunkyard.length < frameStoreJunkyardMax ) { + frameStoreJunkyard.push(this); + } + return null; }; +/******************************************************************************/ /******************************************************************************/ -var pageStoreFactory = function(tabId, pageURL) { - var entry = pageStoreJunkyard.pop(); - if ( entry ) { - return entry.init(tabId, pageURL); - } - return new PageStore(tabId, pageURL); -}; +// To mitigate memory churning +var pageStoreJunkyard = []; +var pageStoreJunkyardMax = 10; + +/******************************************************************************/ + +// Cache only what is worth it if logging is disabled +// http://jsperf.com/string-indexof-vs-object +var collapsibleRequestTypes = 'image sub_frame object'; /******************************************************************************/ @@ -104,6 +270,18 @@ var PageStore = function(tabId, pageURL) { /******************************************************************************/ +PageStore.factory = function(tabId, pageURL) { + var entry = pageStoreJunkyard.pop(); + if ( entry === undefined ) { + entry = new PageStore(tabId, pageURL); + } else { + entry.init(tabId, pageURL); + } + return entry; +}; + +/******************************************************************************/ + PageStore.prototype.init = function(tabId, pageURL) { this.tabId = tabId; this.previousPageURL = ''; @@ -114,26 +292,33 @@ PageStore.prototype.init = function(tabId, pageURL) { // Use hostname if no domain can be extracted this.pageDomain = µb.URI.domainFromHostname(this.pageHostname) || this.pageHostname; - this.frames = disposeFrameStores(this.frames); + this.frames = {}; this.netFiltering = true; this.netFilteringReadTime = 0; this.perLoadBlockedRequestCount = 0; this.perLoadAllowedRequestCount = 0; - this.blockedRequests = {}; - this.allowedRequests = {}; - this.disposeTime = 0; + + this.netFilteringCache = NetFilteringResultCache.factory(); + if ( µb.userSettings.logRequests ) { + this.netFilteringCache.shelfLife = 30 * 60 * 1000; + } + return this; }; /******************************************************************************/ PageStore.prototype.reuse = function(pageURL) { + this.disposeFrameStores(); + this.netFilteringCache = this.netFilteringCache.dispose(); var previousPageURL = this.pageURL; this.init(this.tabId, pageURL); this.previousPageURL = previousPageURL; return this; }; +// https://www.youtube.com/watch?v=dltNSbOupgE + /******************************************************************************/ PageStore.prototype.dispose = function() { @@ -142,11 +327,30 @@ PageStore.prototype.dispose = function() { // sizeable enough chunks (especially requests, through the request URL // used as a key). this.pageURL = ''; + this.previousPageURL = ''; this.pageHostname = ''; this.pageDomain = ''; - if ( pageStoreJunkyard.length < 8 ) { + this.disposeFrameStores(); + this.netFilteringCache = this.netFilteringCache.dispose(); + if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) { pageStoreJunkyard.push(this); } + return null; +}; + +/******************************************************************************/ + +PageStore.prototype.disposeFrameStores = function() { + var frames = this.frames; + if ( typeof frames === 'object' ) { + for ( var k in frames ) { + if ( frames.hasOwnProperty(k) === false ) { + continue; + } + frames[k].dispose(); + } + } + this.frames = {}; }; /******************************************************************************/ @@ -154,7 +358,7 @@ PageStore.prototype.dispose = function() { PageStore.prototype.addFrame = function(frameId, frameURL) { var frameStore = this.frames[frameId]; if ( frameStore === undefined ) { - this.frames[frameId] = frameStore = frameStoreFactory(frameURL); + this.frames[frameId] = frameStore = FrameStore.factory(frameURL); //console.debug('µBlock> PageStore.addFrame(%d, "%s")', frameId, frameURL); } return frameStore; @@ -178,35 +382,26 @@ PageStore.prototype.getNetFilteringSwitch = function() { /******************************************************************************/ -PageStore.prototype.recordRequest = function(type, url, reason) { - var blocked = reason !== false && reason.slice(0, 2) !== '@@'; - - if ( !blocked ) { - this.perLoadAllowedRequestCount++; - µb.localSettings.allowedRequestCount++; - if ( µb.userSettings.logAllowedRequests ) { - this.allowedRequests[url] = type + '\t' + (reason || ''); - } - return; +PageStore.prototype.filterRequest = function(context, requestType, requestURL) { + var result = this.netFilteringCache.lookup(requestURL); + if ( result !== undefined ) { + return result.slice(result.indexOf('\t') + 1); } - - µb.updateBadgeAsync(this.tabId); - - this.perLoadBlockedRequestCount++; - µb.localSettings.blockedRequestCount++; - - // https://github.com/gorhill/uBlock/issues/7 - // https://github.com/gorhill/uBlock/issues/12 - - // No need to record blocked requests which are not image or frame, as - // these are the only ones we try to hide when they are blocked. - if ( µb.userSettings.logBlockedRequests === false ) { - if ( type === 'image' || type === 'sub_frame' ) { - this.blockedRequests[url] = true; - } - return; + //console.debug('µBlock> PageStore.filterRequest(): "%s" not in cache', requestURL); + result = µb.netFilteringEngine.matchString(context, requestURL, requestType); + if ( collapsibleRequestTypes.indexOf(requestType) !== -1 || µb.userSettings.logRequests ) { + this.netFilteringCache.add(requestURL, requestType + '\t' + result); } - this.blockedRequests[url] = type + '\t' + reason; + return result; +}; + +/******************************************************************************/ + +// false: not blocked +// true: blocked + +PageStore.prototype.boolFromResult = function(result) { + return typeof result === 'string' && result !== '' && result.slice(0, 2) !== '@@'; }; /******************************************************************************/ @@ -229,7 +424,7 @@ PageStore.prototype.updateBadge = function() { /******************************************************************************/ return { - factory: pageStoreFactory + factory: PageStore.factory }; })(); diff --git a/js/popup.js b/js/popup.js index 098193497..d985ff810 100644 --- a/js/popup.js +++ b/js/popup.js @@ -59,7 +59,7 @@ var renderStats = function() { // - logging of requests enabled uDom('#gotoLog').toggleClass( 'enabled', - isHTTP && (stats.logBlockedRequests || stats.logAllowedRequests) + isHTTP && stats.logRequests ); // Conditions for element picker: diff --git a/js/stats.js b/js/stats.js index d5586503a..dfd6bb3ae 100644 --- a/js/stats.js +++ b/js/stats.js @@ -31,27 +31,13 @@ messaging.start('stats.js'); /******************************************************************************/ -var logBlockedSettingChanged = function() { +var logSettingChanged = function() { messaging.tell({ what: 'userSettings', - name: 'logBlockedRequests', + name: 'logRequests', value: this.checked }); - uDom('#requests table').toggleClass('hideBlocked', !this.checked); - uDom('#requests').toggleClass('logEnabled', this.checked || uDom('#logAllowedRequests').prop('checked')); - renderPageSelector(); -}; - -/******************************************************************************/ - -var logAllowedSettingChanged = function() { - messaging.tell({ - what: 'userSettings', - name: 'logAllowedRequests', - value: this.checked - }); - uDom('#requests table').toggleClass('hideAllowed', !this.checked); - uDom('#requests').toggleClass('logEnabled', this.checked || uDom('#logBlockedRequests').prop('checked')); + uDom('#requests').toggleClass('logEnabled', this.checked); renderPageSelector(); }; @@ -186,7 +172,7 @@ var pageSelectorChanged = function() { /******************************************************************************/ var renderPageSelector = function(targetTabId) { - if ( !uDom('#logBlockedRequests').prop('checked') && !uDom('#logAllowedRequests').prop('checked') ) { + if ( !uDom('#logRequests').prop('checked') ) { return; } var selectedTabId = targetTabId || parseInt(uDom('#pageSelector').val(), 10); @@ -228,18 +214,14 @@ var renderPageSelector = function(targetTabId) { /******************************************************************************/ var onUserSettingsReceived = function(details) { - uDom('#logBlockedRequests').prop('checked', details.logBlockedRequests); - uDom('#logAllowedRequests').prop('checked', details.logAllowedRequests); - uDom('#requests').toggleClass('logEnabled', details.logBlockedRequests || details.logAllowedRequests); - uDom('#requests table').toggleClass('hideBlocked', !details.logBlockedRequests); - uDom('#requests table').toggleClass('hideAllowed', !details.logAllowedRequests); + uDom('#logRequests').prop('checked', details.logRequests); + uDom('#requests').toggleClass('logEnabled', details.logRequests); var matches = window.location.search.slice(1).match(/(?:^|&)which=(\d+)/); var tabId = matches && matches.length === 2 ? parseInt(matches[1], 10) : 0; renderPageSelector(tabId); - uDom('#logBlockedRequests').on('change', logBlockedSettingChanged); - uDom('#logAllowedRequests').on('change', logAllowedSettingChanged); + uDom('#logRequests').on('change', logSettingChanged); uDom('#refresh').on('click', function() { renderPageSelector(); }); uDom('#pageSelector').on('change', pageSelectorChanged); }; diff --git a/js/tab.js b/js/tab.js index 8369f4410..33eb07e7e 100644 --- a/js/tab.js +++ b/js/tab.js @@ -114,7 +114,11 @@ µBlock.unbindTabFromPageStats = function(tabId) { //console.debug('µBlock> unbindTabFromPageStats(%d)', tabId); - delete this.pageStores[tabId]; + var pageStore = this.pageStores[tabId]; + if ( pageStore !== undefined ) { + pageStore.dispose(); + delete this.pageStores[tabId]; + } }; /******************************************************************************/ diff --git a/js/traffic.js b/js/traffic.js index 36abaf65c..7cc1da211 100644 --- a/js/traffic.js +++ b/js/traffic.js @@ -51,7 +51,6 @@ var onBeforeRequest = function(details) { } var µburi = µb.URI.set(requestURL); - var requestHostname = µburi.hostname; var requestPath = µburi.path; // rhill 2013-12-15: @@ -78,31 +77,35 @@ var onBeforeRequest = function(details) { } } - var reason = false; + var result = ''; if ( pageStore.getNetFilteringSwitch() ) { - reason = µb.netFilteringEngine.matchString(requestContext, requestURL, requestType, requestHostname); + result = pageStore.filterRequest(requestContext, requestType, requestURL); } - // Record what happened. - pageStore.recordRequest(requestType, requestURL, reason); - // Not blocked? - if ( reason === false || reason.slice(0, 2) === '@@' ) { - //console.debug('µBlock> onBeforeRequest()> ALLOW "%s" (%o)', details.url, details); + // Not blocked + if ( pageStore.boolFromResult(result) === false ) { + pageStore.perLoadAllowedRequestCount++; + µb.localSettings.allowedRequestCount++; // https://github.com/gorhill/uBlock/issues/114 if ( frameId > 0 && frameStore === undefined ) { pageStore.addFrame(frameId, requestURL); } + + //console.debug('µBlock> onBeforeRequest()> ALLOW "%s" (%o)', details.url, details); return; } // Blocked - //console.debug('µBlock> onBeforeRequest()> BLOCK "%s" (%o) because "%s"', details.url, details, reason); + pageStore.perLoadBlockedRequestCount++; + µb.localSettings.blockedRequestCount++; + µb.updateBadgeAsync(tabId); // https://github.com/gorhill/uBlock/issues/18 // Do not use redirection, we need to block outright to be sure the request // will not be made. There can be no such guarantee with redirection. + //console.debug('µBlock> onBeforeRequest()> BLOCK "%s" (%o) because "%s"', details.url, details, result); return { 'cancel': true }; }; @@ -166,18 +169,13 @@ var onBeforeSendHeaders = function(details) { // switch of the popup. If so, that would require being able to lookup // a page store from a URL. Have to keep in mind the same URL can appear // in multiple tabs. - var reason = false; + var result = ''; if ( pageStore.getNetFilteringSwitch() ) { - reason = µb.netFilteringEngine.matchStringExactType( - pageDetails, - requestURL, - 'popup', - µburi.hostnameFromURI(requestURL) - ); + result = µb.netFilteringEngine.matchStringExactType(pageDetails, requestURL, 'popup'); } // Not blocked? - if ( reason === false || reason.slice(0, 2) === '@@' ) { + if ( result === '' || result.slice(0, 2) === '@@' ) { return; } diff --git a/stats.html b/stats.html index 83fed3f64..9ec76e873 100644 --- a/stats.html +++ b/stats.html @@ -70,12 +70,6 @@ tr.requestEntry td:nth-of-type(1) { tr.requestEntry td:nth-of-type(2) { text-align: right; } -table.hideBlocked tr.logBlocked { - display: none; - } -table.hideAllowed tr.logAllowed { - display: none; - } tr.logBlocked { background-color: #fff8f8; } @@ -98,12 +92,9 @@ tr.logAllowed ~ tr td:nth-of-type(3) b {