From 715b8e632901a788957594161ff2a324360abae4 Mon Sep 17 00:00:00 2001 From: gorhill Date: Thu, 30 Apr 2015 09:12:50 -0400 Subject: [PATCH] standalone logger + logging of cosmetic filters --- src/background.html | 1 + src/css/devtool-log.css | 20 +++- src/js/contentscript-end.js | 11 +- src/js/contentscript-start.js | 15 ++- src/js/cosmetic-logger.js | 96 +++++++++++++++ src/js/devtool-log.js | 56 ++++++++- src/js/logger.js | 214 ++++++++++++++++++++++++++++++++++ src/js/messaging.js | 99 +++++++++++++--- src/js/pagestore.js | 181 ---------------------------- src/js/tab.js | 1 + src/js/traffic.js | 6 +- src/js/ublock.js | 23 ++++ 12 files changed, 514 insertions(+), 209 deletions(-) create mode 100644 src/js/cosmetic-logger.js create mode 100644 src/js/logger.js diff --git a/src/background.html b/src/background.html index 22de5f3f0..41f12374d 100644 --- a/src/background.html +++ b/src/background.html @@ -22,6 +22,7 @@ + diff --git a/src/css/devtool-log.css b/src/css/devtool-log.css index 4f1f28f1b..5ddf9c9d8 100644 --- a/src/css/devtool-log.css +++ b/src/css/devtool-log.css @@ -68,18 +68,32 @@ input:focus { direction: ltr; width: 100%; } +#content table tr.docBoundary { + background-color: #666; + color: white; + text-align: center; + } +#content table tr.docBoundary > td:first-child { + padding: 1em 0; + white-space: normal; + word-break: break-all; + word-wrap: break-word; + } #content table tr.blocked { - background-color: rgba(192, 0, 0, 0.1) + background-color: rgba(192, 0, 0, 0.1); } #content table tr.allowed { - background-color: rgba(0, 160, 0, 0.1) + background-color: rgba(0, 160, 0, 0.1); } #content table tr.allowed.mirrored { - background-color: rgba(255, 255, 0, 0.3) + background-color: rgba(255, 255, 0, 0.3); } #content table tr.maindoc { background-color: #eee; } +#content table tr.cosmetic { + background-color: rgba(255, 255, 0, 0.1); + } body:not(.filterOff) #content table tr.hidden { display: none; } diff --git a/src/js/contentscript-end.js b/src/js/contentscript-end.js index e5df12d4d..db1af9cb8 100644 --- a/src/js/contentscript-end.js +++ b/src/js/contentscript-end.js @@ -55,6 +55,7 @@ if ( vAPI.contentscriptEndInjected ) { return; } vAPI.contentscriptEndInjected = true; +vAPI.styles = vAPI.styles || []; /******************************************************************************/ /******************************************************************************/ @@ -187,7 +188,7 @@ var uBlockCollapser = (function() { } if ( selectors.length !== 0 ) { messager.send({ - what: 'injectedSelectors', + what: 'cosmeticFiltersInjected', type: 'net', hostname: window.location.hostname, selectors: selectors @@ -385,7 +386,7 @@ var uBlockCollapser = (function() { processHighHighGenericsAsync(); } } - if ( hideSelectors.length ) { + if ( hideSelectors.length !== 0 ) { addStyleTag(hideSelectors); } contextNodes.length = 0; @@ -400,7 +401,7 @@ var uBlockCollapser = (function() { // - Injecting a style tag var addStyleTag = function(selectors) { - var selectorStr = selectors.toString(); + var selectorStr = selectors.join(',\n'); hideElements(selectorStr); var style = document.createElement('style'); // The linefeed before the style block is very important: do no remove! @@ -408,9 +409,10 @@ var uBlockCollapser = (function() { var parent = document.body || document.documentElement; if ( parent ) { parent.appendChild(style); + vAPI.styles.push(style); } messager.send({ - what: 'injectedSelectors', + what: 'cosmeticFiltersInjected', type: 'cosmetic', hostname: window.location.hostname, selectors: selectors @@ -701,6 +703,7 @@ var uBlockCollapser = (function() { idsFromNodeList(selectNodes('[id]')); classesFromNodeList(selectNodes('[class]')); retrieveGenericSelectors(); + messager.send({ what: 'cosmeticFiltersActivated' }); } }; diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js index ad6161d79..9e5210dfb 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -53,6 +53,7 @@ if ( vAPI.contentscriptStartInjected ) { return; } vAPI.contentscriptStartInjected = true; +vAPI.styles = vAPI.styles || []; /******************************************************************************/ @@ -99,6 +100,7 @@ var cosmeticFilters = function(details) { var parent = document.head || document.documentElement; if ( parent ) { parent.appendChild(style); + vAPI.styles.push(style); } } vAPI.donthideCosmeticFilters = donthideCosmeticFilters; @@ -121,6 +123,8 @@ var netFilters = function(details) { }; var filteringHandler = function(details) { + var styleTagCount = vAPI.styles.length; + vAPI.skipCosmeticFiltering = !details || details.skipCosmeticFiltering; if ( details ) { if ( details.cosmeticHide.length !== 0 || details.cosmeticDonthide.length !== 0 ) { @@ -129,8 +133,12 @@ var filteringHandler = function(details) { if ( details.netHide.length !== 0 ) { netFilters(details); } - // The port will never be used again at this point, disconnecting allows - // the browser to flush this script from memory. + } + + // This is just to inform the background process that cosmetic filters were + // actually injected. + if ( vAPI.styles.length !== styleTagCount ) { + localMessager.send({ what: 'cosmeticFiltersActivated' }); } // https://github.com/chrisaljoudi/uBlock/issues/587 @@ -139,7 +147,8 @@ var filteringHandler = function(details) { // cleaned right after browser launch. vAPI.contentscriptStartInjected = details && details.ready; - // Cleanup before leaving + // The port will never be used again at this point, disconnecting allows + // the browser to flush this script from memory. localMessager.close(); }; diff --git a/src/js/cosmetic-logger.js b/src/js/cosmetic-logger.js new file mode 100644 index 000000000..d038d1af6 --- /dev/null +++ b/src/js/cosmetic-logger.js @@ -0,0 +1,96 @@ +/******************************************************************************* + + uBlock - a browser extension to block requests. + Copyright (C) 2015 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +/* global vAPI, HTMLDocument */ + +/******************************************************************************/ + +(function() { + +'use strict'; + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/464 +if ( document instanceof HTMLDocument === false ) { + //console.debug('cosmetic-logger.js > not a HTLMDocument'); + return; +} + +// This can happen +if ( !vAPI ) { + //console.debug('cosmetic-logger.js > vAPI not found'); + return; +} + +/******************************************************************************/ + +var loggedSelectors = vAPI.loggedSelectors || {}; + +var injectedSelectors = []; +var reProperties = /\s*\{[^}]+\}\s*/; +var i; +var styles = vAPI.styles || []; + +i = styles.length; +while ( i-- ) { + injectedSelectors = injectedSelectors.concat(styles[i].textContent.replace(reProperties, '').split(/\s*,\n\s*/)); +} + +if ( injectedSelectors.length === 0 ) { + return; +} + +var matchedSelectors = []; +var selector; + +i = injectedSelectors.length; +while ( i-- ) { + selector = injectedSelectors[i]; + if ( loggedSelectors.hasOwnProperty(selector) ) { + continue; + } + if ( document.querySelector(selector) === null ) { + continue; + } + loggedSelectors[selector] = true; + matchedSelectors.push(selector); +} + +vAPI.loggedSelectors = loggedSelectors; + +/******************************************************************************/ + +var localMessager = vAPI.messaging.channel('cosmetic-*.js'); + +localMessager.send({ + what: 'logCosmeticFilteringData', + pageURL: window.location.href, + matchedSelectors: matchedSelectors +}, function() { + localMessager.close(); +}); + +/******************************************************************************/ + +})(); + +/******************************************************************************/ diff --git a/src/js/devtool-log.js b/src/js/devtool-log.js index 2720e1bd0..7eb48781c 100644 --- a/src/js/devtool-log.js +++ b/src/js/devtool-log.js @@ -37,7 +37,8 @@ var inspectedTabId = ''; var doc = document; var body = doc.body; var tbody = doc.querySelector('#content tbody'); -var rowJunkyard = []; +var row1Junkyard = []; +var row4Junkyard = []; var reFilter = null; var filterTargetTestResult = true; var maxEntries = 0; @@ -106,7 +107,7 @@ var renderURL = function(url, filter) { /******************************************************************************/ var createRow = function() { - var tr = rowJunkyard.pop(); + var tr = row4Junkyard.pop(); if ( tr ) { tr.className = ''; return tr; @@ -121,8 +122,35 @@ var createRow = function() { /******************************************************************************/ +var createGap = function(url) { + var tr = row1Junkyard.pop(); + if ( !tr ) { + tr = doc.createElement('tr'); + tr.classList.add('docBoundary'); + tr.appendChild(doc.createElement('td')); + tr.cells[0].setAttribute('colspan', '4'); + } + tr.cells[0].textContent = url; + tbody.insertBefore(tr, tbody.firstChild); +}; + +/******************************************************************************/ + var renderLogEntry = function(entry) { var tr = createRow(); + + // If the request is that of a root frame, insert a gap in the table + // in order to visually separate entries for different documents. + if ( entry.type === 'main_frame' ) { + createGap(entry.url); + tr.classList.add('maindoc'); + } + + // Cosmetic filter? + if ( entry.result.charAt(0) === 'c' ) { + tr.classList.add('cosmetic'); + } + if ( entry.result.charAt(1) === 'b' ) { tr.classList.add('blocked'); tr.cells[0].textContent = ' -\u00A0'; @@ -135,9 +163,11 @@ var renderLogEntry = function(entry) { } else { tr.cells[0].textContent = ''; } + if ( entry.type === 'main_frame' ) { tr.classList.add('maindoc'); } + var filterText = entry.result.slice(3); if ( entry.result.lastIndexOf('sa', 0) === 0 ) { filterText = '@@' + filterText; @@ -198,8 +228,17 @@ var truncateLog = function(size) { size = 25000; } size = Math.min(size, 25000); + var tr; while ( tbody.childElementCount > size ) { - rowJunkyard.push(tbody.removeChild(tbody.lastElementChild)); + tr = tbody.lastElementChild; + // https://github.com/gorhill/uBlock/issues/123 + // Triage according to row type. + if ( tr.cells.length === 1 ) { + row1Junkyard.push(tr); + } else { + row4Junkyard.push(tr); + } + tbody.removeChild(tr); } }; @@ -225,8 +264,17 @@ var readLogBuffer = function() { /******************************************************************************/ var clearBuffer = function() { + var tr; while ( tbody.firstChild !== null ) { - rowJunkyard.push(tbody.removeChild(tbody.firstChild)); + tr = tbody.lastElementChild; + // https://github.com/gorhill/uBlock/issues/123 + // Triage according to row type. + if ( tr.cells.length === 1 ) { + row1Junkyard.push(tr); + } else { + row4Junkyard.push(tr); + } + tbody.removeChild(tr); } }; diff --git a/src/js/logger.js b/src/js/logger.js new file mode 100644 index 000000000..dc9e3a3c8 --- /dev/null +++ b/src/js/logger.js @@ -0,0 +1,214 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +/* global µBlock */ + +/******************************************************************************/ +/******************************************************************************/ + +µBlock.logger = (function() { + +'use strict'; + +/******************************************************************************/ +/******************************************************************************/ + +var LogEntry = function(details, result) { + this.init(details, result); +}; + +/******************************************************************************/ + +var logEntryFactory = function(details, result) { + var entry = logEntryJunkyard.pop(); + if ( entry ) { + return entry.init(details, result); + } + return new LogEntry(details, result); +}; + +var logEntryJunkyard = []; +var logEntryJunkyardMax = 100; + +/******************************************************************************/ + +LogEntry.prototype.init = function(details, result) { + this.tstamp = Date.now(); + this.url = details.requestURL; + this.hostname = details.requestHostname; + this.type = details.requestType; + this.result = result; + return this; +}; + +/******************************************************************************/ + +LogEntry.prototype.dispose = function() { + this.url = this.hostname = this.type = this.result = ''; + if ( logEntryJunkyard.length < logEntryJunkyardMax ) { + logEntryJunkyard.push(this); + } +}; + +/******************************************************************************/ +/******************************************************************************/ + +var LogBuffer = function() { + this.lastReadTime = 0; + this.size = 50; + this.buffer = new Array(this.size); + this.readPtr = 0; + this.writePtr = 0; +}; + +/******************************************************************************/ + +LogBuffer.prototype.dispose = function() { + var entry; + var i = this.buffer.length; + while ( i-- ) { + entry = this.buffer[i]; + if ( entry instanceof LogEntry ) { + entry.dispose(); + } + } + this.buffer = null; + return null; +}; + +/******************************************************************************/ + +LogBuffer.prototype.writeOne = function(details, result) { + // Reusing log entry = less memory churning + var entry = this.buffer[this.writePtr]; + if ( entry instanceof LogEntry === false ) { + this.buffer[this.writePtr] = logEntryFactory(details, result); + } else { + entry.init(details, result); + } + this.writePtr += 1; + if ( this.writePtr === this.size ) { + this.writePtr = 0; + } + // Grow the buffer between 1.5x-2x the current size + if ( this.writePtr === this.readPtr ) { + var toMove = this.buffer.slice(0, this.writePtr); + var minSize = Math.ceil(this.size * 1.5); + this.size += toMove.length; + if ( this.size < minSize ) { + this.buffer = this.buffer.concat(toMove, new Array(minSize - this.size)); + this.writePtr = this.size; + } else { + this.buffer = this.buffer.concat(toMove); + this.writePtr = 0; + } + this.size = this.buffer.length; + } +}; + +/******************************************************************************/ + +LogBuffer.prototype.readAll = function() { + var out; + if ( this.readPtr < this.writePtr ) { + out = this.buffer.slice(this.readPtr, this.writePtr); + } else if ( this.writePtr < this.readPtr ) { + out = this.buffer.slice(this.readPtr).concat(this.buffer.slice(0, this.writePtr)); + } else { + out = []; + } + this.readPtr = this.writePtr; + this.lastReadTime = Date.now(); + return out; +}; + +/******************************************************************************/ +/******************************************************************************/ + +// Tab id to log buffer instances +var logBuffers = {}; + +// After 30 seconds without being read, a buffer will be considered unused, and +// thus removed from memory. +var logBufferObsoleteAfter = 30 * 1000; + +/******************************************************************************/ + +var writeOne = function(tabId, details, result) { + if ( logBuffers.hasOwnProperty(tabId) === false ) { + return; + } + var logBuffer = logBuffers[tabId]; + logBuffer.writeOne(details, result); +}; + +/******************************************************************************/ + +var readAll = function(tabId) { + if ( logBuffers.hasOwnProperty(tabId) === false ) { + logBuffers[tabId] = new LogBuffer(); + } + return logBuffers[tabId].readAll(); +}; + +/******************************************************************************/ + +var isObserved = function(tabId) { + return logBuffers.hasOwnProperty(tabId); +}; + +/******************************************************************************/ + +var loggerJanitor = function() { + var logBuffer; + var obsolete = Date.now() - logBufferObsoleteAfter; + for ( var tabId in logBuffers ) { + if ( logBuffers.hasOwnProperty(tabId) === false ) { + continue; + } + logBuffer = logBuffers[tabId]; + if ( logBuffer.lastReadTime < obsolete ) { + logBuffer.dispose(); + delete logBuffers[tabId]; + } + } + setTimeout(loggerJanitor, loggerJanitorPeriod); +}; + +// The janitor will look for stale log buffer every 2 minutes. +var loggerJanitorPeriod = 2 * 60 * 1000; + +setTimeout(loggerJanitor, loggerJanitorPeriod); + +/******************************************************************************/ + +return { + writeOne: writeOne, + readAll: readAll, + isObserved: isObserved +}; + +/******************************************************************************/ +/******************************************************************************/ + +})(); + +/******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index 595e8e5b2..492aedf69 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -50,6 +50,8 @@ var onMessage = function(request, sender, callback) { break; } + var tabId = sender && sender.tab ? sender.tab.id : 0; + // Sync var response; @@ -59,6 +61,17 @@ var onMessage = function(request, sender, callback) { µb.contextMenuClientY = request.clientY; break; + case 'cosmeticFiltersInjected': + // Is this a request to cache selectors? + µb.cosmeticFilteringEngine.addToSelectorCache(request); + /* falls through */ + case 'cosmeticFiltersActivated': + // Net-based cosmetic filters are of no interest for logging purpose. + if ( µb.logger.isObserved(tabId) && request.type !== 'net' ) { + µb.logCosmeticFilters(tabId); + } + break; + case 'forceUpdateAssets': µb.assetUpdater.force(); break; @@ -463,9 +476,9 @@ var filterRequests = function(pageStore, details) { /******************************************************************************/ -var onMessage = function(details, sender, callback) { +var onMessage = function(request, sender, callback) { // Async - switch ( details.what ) { + switch ( request.what ) { default: break; } @@ -478,21 +491,17 @@ var onMessage = function(details, sender, callback) { pageStore = µb.pageStoreFromTabId(sender.tab.id); } - switch ( details.what ) { + switch ( request.what ) { case 'retrieveGenericCosmeticSelectors': response = { shutdown: !pageStore || !pageStore.getNetFilteringSwitch(), result: null }; if ( !response.shutdown && pageStore.getGenericCosmeticFilteringSwitch() ) { - response.result = µb.cosmeticFilteringEngine.retrieveGenericSelectors(details); + response.result = µb.cosmeticFilteringEngine.retrieveGenericSelectors(request); } break; - case 'injectedSelectors': - µb.cosmeticFilteringEngine.addToSelectorCache(details); - break; - // Evaluate many requests case 'filterRequests': response = { @@ -500,7 +509,7 @@ var onMessage = function(details, sender, callback) { result: null }; if(!response.shutdown) { - response.result = filterRequests(pageStore, details); + response.result = filterRequests(pageStore, request); } break; @@ -520,6 +529,73 @@ vAPI.messaging.listen('contentscript-end.js', onMessage); /******************************************************************************/ /******************************************************************************/ +// cosmetic-*.js + +(function() { + +'use strict'; + +/******************************************************************************/ + +var µb = µBlock; + +/******************************************************************************/ + +var logCosmeticFilters = function(tabId, details) { + if ( µb.logger.isObserved(tabId) === false ) { + return; + } + + var context = { + requestURL: details.pageURL, + requestHostname: µb.URI.hostnameFromURI(details.pageURL), + requestType: 'dom' + }; + + var selectors = details.matchedSelectors; + + selectors.sort(); + + for ( var i = 0; i < selectors.length; i++ ) { + µb.logger.writeOne(tabId, context, 'cb:##' + selectors[i]); + } +}; + +/******************************************************************************/ + +var onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + default: + break; + } + + // Sync + var response; + + var tabId = sender && sender.tab ? sender.tab.id : 0; + + switch ( request.what ) { + case 'logCosmeticFilteringData': + logCosmeticFilters(tabId, request); + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen('cosmetic-*.js', onMessage); + +/******************************************************************************/ + +})(); + +/******************************************************************************/ +/******************************************************************************/ + // element-picker.js (function() { @@ -1134,10 +1210,7 @@ var onMessage = function(request, sender, callback) { switch ( request.what ) { case 'readLogBuffer': - var pageStore = µb.pageStoreFromTabId(request.tabId); - if ( pageStore ) { - response = pageStore.logBuffer.readAll(); - } + response = µb.logger.readAll(request.tabId); break; default: diff --git a/src/js/pagestore.js b/src/js/pagestore.js index f0793c4c8..657e76115 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -45,178 +45,6 @@ var µb = µBlock; /******************************************************************************/ /******************************************************************************/ -var LogEntry = function(details, result) { - this.init(details, result); -}; - -/******************************************************************************/ - -var logEntryFactory = function(details, result) { - var entry = logEntryJunkyard.pop(); - if ( entry ) { - return entry.init(details, result); - } - return new LogEntry(details, result); -}; - -var logEntryJunkyard = []; -var logEntryJunkyardMax = 100; - -/******************************************************************************/ - -LogEntry.prototype.init = function(details, result) { - this.tstamp = Date.now(); - this.url = details.requestURL; - this.hostname = details.requestHostname; - this.type = details.requestType; - this.result = result; - return this; -}; - -/******************************************************************************/ - -LogEntry.prototype.dispose = function() { - this.url = this.hostname = this.type = this.result = ''; - if ( logEntryJunkyard.length < logEntryJunkyardMax ) { - logEntryJunkyard.push(this); - } -}; - -/******************************************************************************/ - -var LogBuffer = function() { - this.lastReadTime = 0; - this.size = 50; - this.buffer = null; - this.readPtr = 0; - this.writePtr = 0; -}; - -/******************************************************************************/ - -var logBufferFactory = function() { - return new LogBuffer(); -}; - -var liveLogBuffers = []; - -/******************************************************************************/ - -LogBuffer.prototype.dispose = function() { - if ( this.buffer === null ) { - return null; - } - var entry; - var i = this.buffer.length; - while ( i-- ) { - entry = this.buffer[i]; - if ( entry instanceof LogEntry ) { - entry.dispose(); - } - } - this.buffer = null; - return null; -}; - -/******************************************************************************/ - -LogBuffer.prototype.start = function() { - if ( this.buffer === null ) { - this.buffer = new Array(this.size); - this.readPtr = 0; - this.writePtr = 0; - liveLogBuffers.push(this); - } -}; - -/******************************************************************************/ - -LogBuffer.prototype.stop = function() { - this.dispose(); - this.buffer = null; - // The janitor will remove us from the live pool eventually. -}; - -/******************************************************************************/ - -LogBuffer.prototype.writeOne = function(details, result) { - if ( this.buffer === null ) { - return; - } - // Reusing log entry = less memory churning - var entry = this.buffer[this.writePtr]; - if ( entry instanceof LogEntry === false ) { - this.buffer[this.writePtr] = logEntryFactory(details, result); - } else { - entry.init(details, result); - } - this.writePtr += 1; - if ( this.writePtr === this.size ) { - this.writePtr = 0; - } - // Grow the buffer between 1.5x-2x the current size - if ( this.writePtr === this.readPtr ) { - var toMove = this.buffer.slice(0, this.writePtr); - var minSize = Math.ceil(this.size * 1.5); - this.size += toMove.length; - if ( this.size < minSize ) { - this.buffer = this.buffer.concat(toMove, new Array(minSize - this.size)); - this.writePtr = this.size; - } else { - this.buffer = this.buffer.concat(toMove); - this.writePtr = 0; - } - this.size = this.buffer.length; - } -}; - -/******************************************************************************/ - -LogBuffer.prototype.readAll = function() { - var out; - if ( this.buffer === null ) { - this.start(); - out = []; - } else if ( this.readPtr < this.writePtr ) { - out = this.buffer.slice(this.readPtr, this.writePtr); - } else if ( this.writePtr < this.readPtr ) { - out = this.buffer.slice(this.readPtr).concat(this.buffer.slice(0, this.writePtr)); - } else { - out = []; - } - this.readPtr = this.writePtr; - this.lastReadTime = Date.now(); - return out; -}; - -/******************************************************************************/ - -var logBufferJanitor = function() { - var logBuffer; - var obsolete = Date.now() - logBufferObsoleteAfter; - var i = liveLogBuffers.length; - while ( i-- ) { - logBuffer = liveLogBuffers[i]; - if ( logBuffer.lastReadTime < obsolete ) { - logBuffer.stop(); - liveLogBuffers.splice(i, 1); - } - } - setTimeout(logBufferJanitor, logBufferJanitorPeriod); -}; - -// The janitor will look for stale log buffer every 2 minutes. -var logBufferJanitorPeriod = 2 * 60 * 1000; - -// After 30 seconds without being read, a buffer will be considered unused, and -// thus removed from memory. -var logBufferObsoleteAfter = 30 * 1000; - -setTimeout(logBufferJanitor, logBufferJanitorPeriod); - -/******************************************************************************/ -/******************************************************************************/ - // To mitigate memory churning var netFilteringResultCacheEntryJunkyard = []; var netFilteringResultCacheEntryJunkyardMax = 200; @@ -483,12 +311,6 @@ PageStore.prototype.init = function(tabId) { .matchStringExactType(context, tabContext.normalURL, 'cosmetic-filtering') .charAt(1) === 'b'; - // Preserve old buffer if there is one already, it may be in use, and - // overwritting it would required another read to restart it. - if ( this.logBuffer instanceof LogBuffer === false ) { - this.logBuffer = logBufferFactory(); - } - return this; }; @@ -538,7 +360,6 @@ PageStore.prototype.dispose = function() { this.hostnameToCountMap = null; this.disposeFrameStores(); this.netFilteringCache = this.netFilteringCache.dispose(); - this.logBuffer = this.logBuffer.dispose(); if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) { pageStoreJunkyard.push(this); } @@ -703,7 +524,6 @@ PageStore.prototype.filterRequest = function(context) { return result; }; -// Cache only what is worth it if logging is disabled // http://jsperf.com/string-indexof-vs-object var collapsibleRequestTypes = 'image sub_frame object'; @@ -766,7 +586,6 @@ PageStore.prototype.logRequest = function(context, result) { µb.localSettings.blockedRequestCount++; } µb.localSettingsModifyTime = now; - this.logBuffer.writeOne(context, result); }; /******************************************************************************/ diff --git a/src/js/tab.js b/src/js/tab.js index 1dda0a6c5..c0a5a3a3d 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -470,6 +470,7 @@ vAPI.tabs.onPopup = function(details) { if ( pageStore ) { pageStore.logRequest(context, result); } + µb.logger.writeOne(details.openerTabId, context, result); // Not blocked if ( µb.isAllowResult(result) ) { diff --git a/src/js/traffic.js b/src/js/traffic.js index 41db50732..763a44542 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -92,6 +92,7 @@ var onBeforeRequest = function(details) { // Possible outcomes: blocked, allowed-passthru, allowed-mirror pageStore.logRequest(requestContext, result); + µb.logger.writeOne(tabId, requestContext, result); // Not blocked if ( µb.isAllowResult(result) ) { @@ -152,7 +153,7 @@ var onBeforeRootFrameRequest = function(details) { if ( pageStore ) { pageStore.logRequest(context, result); } - return; + µb.logger.writeOne(tabId, context, result); }; /******************************************************************************/ @@ -225,6 +226,7 @@ var onBeforeBehindTheSceneRequest = function(details) { } pageStore.logRequest(context, result); + µb.logger.writeOne(vAPI.noTabId, context, result); // Not blocked if ( µb.isAllowResult(result) ) { @@ -272,6 +274,7 @@ var onHeadersReceived = function(details) { var result = pageStore.filterRequestNoCache(context); pageStore.logRequest(context, result); + µb.logger.writeOne(tabId, context, result); // Don't block if ( µb.isAllowResult(result) ) { @@ -318,6 +321,7 @@ var onRootFrameHeadersReceived = function(details) { var result = pageStore.filterRequest(context); pageStore.logRequest(context, result); + µb.logger.writeOne(tabId, context, result); // Don't block if ( µb.isAllowResult(result) ) { diff --git a/src/js/ublock.js b/src/js/ublock.js index 0fd81e495..b713df96d 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -318,4 +318,27 @@ var matchWhitelistDirective = function(url, hostname, directive) { /******************************************************************************/ +µBlock.logCosmeticFilters = (function() { + var tabIdToTimerMap = {}; + + var injectNow = function(tabId) { + delete tabIdToTimerMap[tabId]; + vAPI.tabs.injectScript(tabId, { file: 'js/cosmetic-logger.js' }); + }; + + var injectAsync = function(tabId) { + if ( tabIdToTimerMap.hasOwnProperty(tabId) ) { + return; + } + tabIdToTimerMap[tabId] = setTimeout( + injectNow.bind(null, tabId), + 100 + ); + }; + + return injectAsync; +})(); + +/******************************************************************************/ + })();