diff --git a/src/css/devtool-log.css b/src/css/devtool-log.css index 76ad1be36..d9d2f7f8d 100644 --- a/src/css/devtool-log.css +++ b/src/css/devtool-log.css @@ -70,6 +70,9 @@ body.colorBlind #content table tr.allowed { #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 2c394605a..ed2888060 100644 --- a/src/js/contentscript-end.js +++ b/src/js/contentscript-end.js @@ -169,7 +169,7 @@ var uBlockCollapser = (function() { } if ( selectors.length !== 0 ) { messager.send({ - what: 'injectedSelectors', + what: 'cosmeticFiltersInjected', type: 'net', hostname: window.location.hostname, selectors: selectors @@ -385,7 +385,7 @@ var uBlockCollapser = (function() { // - Injecting a style tag var addStyleTag = function(selectors) { - var selectorStr = selectors.toString(); + var selectorStr = selectors.join(',\n'); var style = document.createElement('style'); // The linefeed before the style block is very important: do no remove! style.appendChild(document.createTextNode(selectorStr + '\n{display:none !important;}')); @@ -396,7 +396,7 @@ var uBlockCollapser = (function() { } hideElements(selectorStr); messager.send({ - what: 'injectedSelectors', + what: 'cosmeticFiltersInjected', type: 'cosmetic', hostname: window.location.hostname, selectors: selectors diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js index a7f7cbbfa..8bdc80cab 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -123,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 ) { @@ -135,6 +137,12 @@ var filteringHandler = function(details) { // 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: 'cosmeticFiltersInjected' }); + } + // https://github.com/chrisaljoudi/uBlock/issues/587 // If no filters were found, maybe the script was injected before uBlock's // process was fully initialized. When this happens, pages won't be diff --git a/src/js/cosmetic-logger.js b/src/js/cosmetic-logger.js new file mode 100644 index 000000000..f2c9f82e4 --- /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; + } + loggedSelectors[selector] = true; + if ( document.querySelector(selector) === null ) { + continue; + } + 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/cosmetic-count.js b/src/js/cosmetic-survey.js similarity index 76% rename from src/js/cosmetic-count.js rename to src/js/cosmetic-survey.js index 3dcee09ce..532bf9b65 100644 --- a/src/js/cosmetic-count.js +++ b/src/js/cosmetic-survey.js @@ -31,13 +31,13 @@ // https://github.com/gorhill/uBlock/issues/464 if ( document instanceof HTMLDocument === false ) { - //console.debug('cosmetic-on.js > not a HTLMDocument'); + //console.debug('cosmetic-survey.js > not a HTLMDocument'); return; } // This can happen if ( !vAPI ) { - //console.debug('cosmetic-count.js > no vAPI'); + //console.debug('cosmetic-survey.js > vAPI not found'); return; } @@ -45,23 +45,20 @@ if ( !vAPI ) { // Insert all cosmetic filtering-related style tags in the DOM -var selectors = []; +var injectedSelectors = []; +var filteredElementCount = 0; + var reProperties = /\s*\{[^}]+\}\s*/; var i; var styles = vAPI.styles || []; i = styles.length; while ( i-- ) { - selectors.push(styles[i].textContent.replace(reProperties, '')); + injectedSelectors = injectedSelectors.concat(styles[i].textContent.replace(reProperties, '').split(/\s*,\n\s*/)); } -var elems = []; - -if ( selectors.length !== 0 ) { - try { - elems = document.querySelectorAll(selectors.join(',')); - } catch (e) { - } +if ( injectedSelectors.length !== 0 ) { + filteredElementCount = document.querySelectorAll(injectedSelectors.join(',')).length; } /******************************************************************************/ @@ -69,8 +66,10 @@ if ( selectors.length !== 0 ) { var localMessager = vAPI.messaging.channel('cosmetic-*.js'); localMessager.send({ - what: 'hiddenElementCount', - count: elems.length + what: 'liveCosmeticFilteringData', + pageURL: window.location.href, + filteredElementCount: filteredElementCount, + injectedSelectors: injectedSelectors }, function() { localMessager.close(); }); diff --git a/src/js/devtool-log.js b/src/js/devtool-log.js index 5862beb48..e2e78e78a 100644 --- a/src/js/devtool-log.js +++ b/src/js/devtool-log.js @@ -123,6 +123,12 @@ var createRow = function() { var renderLogEntry = function(entry) { var tr = createRow(); + + // 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,13 +141,16 @@ 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; } + tr.cells[1].textContent = filterText + '\t'; tr.cells[2].textContent = (prettyRequestTypes[entry.type] || entry.type) + '\t'; vAPI.insertHTML(tr.cells[3], renderURL(entry.url, entry.result)); diff --git a/src/js/logger.js b/src/js/logger.js index eb77e0765..dc9e3a3c8 100644 --- a/src/js/logger.js +++ b/src/js/logger.js @@ -152,7 +152,7 @@ var logBufferObsoleteAfter = 30 * 1000; /******************************************************************************/ -var loggerWriteOne = function(tabId, details, result) { +var writeOne = function(tabId, details, result) { if ( logBuffers.hasOwnProperty(tabId) === false ) { return; } @@ -162,7 +162,7 @@ var loggerWriteOne = function(tabId, details, result) { /******************************************************************************/ -var loggerReadAll = function(tabId) { +var readAll = function(tabId) { if ( logBuffers.hasOwnProperty(tabId) === false ) { logBuffers[tabId] = new LogBuffer(); } @@ -171,6 +171,12 @@ var loggerReadAll = function(tabId) { /******************************************************************************/ +var isObserved = function(tabId) { + return logBuffers.hasOwnProperty(tabId); +}; + +/******************************************************************************/ + var loggerJanitor = function() { var logBuffer; var obsolete = Date.now() - logBufferObsoleteAfter; @@ -195,8 +201,9 @@ setTimeout(loggerJanitor, loggerJanitorPeriod); /******************************************************************************/ return { - writeOne: loggerWriteOne, - readAll: loggerReadAll + writeOne: writeOne, + readAll: readAll, + isObserved: isObserved }; /******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index e880789ec..f7dcedb33 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -37,67 +37,80 @@ var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { - case 'getAssetContent': - // https://github.com/chrisaljoudi/uBlock/issues/417 - µb.assets.get(request.url, callback); - return; + case 'getAssetContent': + // https://github.com/chrisaljoudi/uBlock/issues/417 + µb.assets.get(request.url, callback); + return; - case 'reloadAllFilters': - µb.reloadAllFilters(callback); - return; + case 'reloadAllFilters': + µb.reloadAllFilters(callback); + return; - default: - break; + default: + break; } + var tabId = sender && sender.tab ? sender.tab.id : 0; + // Sync var response; switch ( request.what ) { - case 'contextMenuEvent': - µb.contextMenuClientX = request.clientX; - µb.contextMenuClientY = request.clientY; - break; + case 'contextMenuEvent': + µb.contextMenuClientX = request.clientX; + µb.contextMenuClientY = request.clientY; + break; - case 'forceUpdateAssets': - µb.assetUpdater.force(); - break; + case 'cosmeticFiltersInjected': + // Is this a request to cache selectors? + if ( Array.isArray(request.selectors) ) { + µb.cosmeticFilteringEngine.addToSelectorCache(request); + } + // Net-based cosmetic filters are of no interest for logging purpose. + if ( µb.logger.isObserved(tabId) && request.type !== 'net' ) { + µb.logCosmeticFilters(tabId); + } + break; - case 'getAppData': - response = {name: vAPI.app.name, version: vAPI.app.version}; - break; + case 'forceUpdateAssets': + µb.assetUpdater.force(); + break; - case 'getUserSettings': - response = µb.userSettings; - break; + case 'getAppData': + response = {name: vAPI.app.name, version: vAPI.app.version}; + break; - case 'gotoURL': - vAPI.tabs.open(request.details); - break; + case 'getUserSettings': + response = µb.userSettings; + break; - case 'reloadTab': - if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) { - vAPI.tabs.reload(request.tabId); - if ( request.select && vAPI.tabs.select ) { - vAPI.tabs.select(request.tabId); - } + case 'gotoURL': + vAPI.tabs.open(request.details); + break; + + case 'reloadTab': + if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) { + vAPI.tabs.reload(request.tabId); + if ( request.select && vAPI.tabs.select ) { + vAPI.tabs.select(request.tabId); } - break; + } + break; - case 'selectFilterLists': - µb.selectFilterLists(request.switches); - break; + case 'selectFilterLists': + µb.selectFilterLists(request.switches); + break; - case 'toggleHostnameSwitch': - µb.toggleHostnameSwitch(request); - break; + case 'toggleHostnameSwitch': + µb.toggleHostnameSwitch(request); + break; - case 'userSettings': - response = µb.changeUserSettings(request.name, request.value); - break; + case 'userSettings': + response = µb.changeUserSettings(request.name, request.value); + break; - default: - return vAPI.messaging.UNHANDLED; + default: + return vAPI.messaging.UNHANDLED; } callback(response); @@ -296,7 +309,7 @@ var getPopupDataLazy = function(tabId, callback) { return; } - µb.getHiddenElementCount(tabId, function() { + µb.surveyCosmeticFilters(tabId, function() { r.hiddenElementCount = pageStore.hiddenElementCount; callback(r); }); @@ -497,11 +510,11 @@ var filterRequests = function(pageStore, details) { /******************************************************************************/ -var onMessage = function(details, sender, callback) { +var onMessage = function(request, sender, callback) { // Async - switch ( details.what ) { - default: - break; + switch ( request.what ) { + default: + break; } // Sync @@ -512,33 +525,29 @@ var onMessage = function(details, sender, callback) { pageStore = µb.pageStoreFromTabId(sender.tab.id); } - switch ( details.what ) { - case 'retrieveGenericCosmeticSelectors': - response = { - shutdown: !pageStore || !pageStore.getNetFilteringSwitch(), - result: null - }; - if ( !response.shutdown && pageStore.getGenericCosmeticFilteringSwitch() ) { - response.result = µb.cosmeticFilteringEngine.retrieveGenericSelectors(details); - } - break; + switch ( request.what ) { + case 'retrieveGenericCosmeticSelectors': + response = { + shutdown: !pageStore || !pageStore.getNetFilteringSwitch(), + result: null + }; + if ( !response.shutdown && pageStore.getGenericCosmeticFilteringSwitch() ) { + response.result = µb.cosmeticFilteringEngine.retrieveGenericSelectors(request); + } + break; - case 'injectedSelectors': - µb.cosmeticFilteringEngine.addToSelectorCache(details); - break; + case 'filterRequests': + response = { + shutdown: !pageStore || !pageStore.getNetFilteringSwitch(), + result: null + }; + if ( !response.shutdown ) { + response.result = filterRequests(pageStore, request); + } + break; - case 'filterRequests': - response = { - shutdown: !pageStore || !pageStore.getNetFilteringSwitch(), - result: null - }; - if ( !response.shutdown ) { - response.result = filterRequests(pageStore, details); - } - break; - - default: - return vAPI.messaging.UNHANDLED; + default: + return vAPI.messaging.UNHANDLED; } callback(response); @@ -565,6 +574,28 @@ var µb = µBlock; /******************************************************************************/ +var logCosmeticFilters = function(tabId, details) { + if ( µb.logger.isObserved(tabId) === false ) { + return; + } + + var context = { + requestURL: '', + 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 ) { @@ -575,20 +606,22 @@ var onMessage = function(request, sender, callback) { // Sync var response; - var pageStore; - if ( sender && sender.tab ) { - pageStore = µb.pageStoreFromTabId(sender.tab.id); - } + var tabId = sender && sender.tab ? sender.tab.id : 0; switch ( request.what ) { - case 'hiddenElementCount': - if ( pageStore ) { - pageStore.hiddenElementCount = request.count; - } - break; + case 'liveCosmeticFilteringData': + var pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore ) { + pageStore.hiddenElementCount = request.filteredElementCount; + } + break; - default: - return vAPI.messaging.UNHANDLED; + case 'logCosmeticFilteringData': + logCosmeticFilters(tabId, request); + break; + + default: + return vAPI.messaging.UNHANDLED; } callback(response); diff --git a/src/js/ublock.js b/src/js/ublock.js index 93460227f..dfd6e81c3 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -338,15 +338,38 @@ var matchWhitelistDirective = function(url, hostname, directive) { /******************************************************************************/ -µBlock.getHiddenElementCount = function(tabId, callback) { +µBlock.surveyCosmeticFilters = function(tabId, callback) { callback = callback || this.noopFunc; if ( vAPI.isBehindTheSceneTabId(tabId) ) { callback(); return; } - vAPI.tabs.injectScript(tabId, { file: 'js/cosmetic-count.js' }, callback); + vAPI.tabs.injectScript(tabId, { file: 'js/cosmetic-survey.js' }, callback); }; /******************************************************************************/ +µ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; +})(); + +/******************************************************************************/ + })();