diff --git a/platform/chromium/vapi-client.js b/platform/chromium/vapi-client.js index 78891bf96..5285e9338 100644 --- a/platform/chromium/vapi-client.js +++ b/platform/chromium/vapi-client.js @@ -67,6 +67,23 @@ if ( vAPI.sessionId ) { /******************************************************************************/ +var referenceCounter = 0; + +vAPI.lock = function() { + referenceCounter += 1; +}; + +vAPI.unlock = function() { + referenceCounter -= 1; + if ( referenceCounter === 0 ) { + // Eventually there will be code here to flush the javascript code + // from this file out of memory when it ends up unused. + + } +}; + +/******************************************************************************/ + vAPI.executionCost = { start: function(){}, stop: function(){} diff --git a/platform/firefox/vapi-client.js b/platform/firefox/vapi-client.js index 88843ea5a..e4bfca544 100644 --- a/platform/firefox/vapi-client.js +++ b/platform/firefox/vapi-client.js @@ -56,6 +56,18 @@ var vAPI = self.vAPI = self.vAPI || {}; /******************************************************************************/ +var referenceCounter = 0; + +vAPI.lock = function() { + referenceCounter += 1; +}; + +vAPI.unlock = function() { + referenceCounter -= 1; +}; + +/******************************************************************************/ + vAPI.executionCost = { start: function(){}, stop: function(){} diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 0da8d9c8d..bcc16345d 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -21,9 +21,50 @@ 'use strict'; -/******************************************************************************/ +/******************************************************************************* -// Injected into content pages + +--> [[domSurveyor] --> domFilterer] + domWatcher--| + +--> [domCollapser] + + domWatcher: + Watches for changes in the DOM, and notify the other components about these + changes. + + domCollapser: + Enforces the collapsing of DOM elements for which a corresponding + resource was blocked through network filtering. + + domFilterer: + Enforces the filtering of DOM elements, by feeding it cosmetic filters. + + domSurveyor: + Surveys the DOM to find new cosmetic filters to apply to the current page. + + If page is whitelisted: + - domWatcher: off + - domCollapser: off + - domFilterer: off + - domSurveyor: off + I verified that the code in this file is completely flushed out of memory + when a page is whitelisted. + + If cosmetic filtering is disabled: + - domWatcher: on + - domCollapser: on + - domFilterer: off + - domSurveyor: off + + If generic cosmetic filtering is disabled: + - domWatcher: on + - domCollapser: on + - domFilterer: on + - domSurveyor: off + + Additionally, the domSurveyor can turn itself off once it decides that + it has become pointless (repeatedly not finding new cosmetic filters). + +*/ /******************************************************************************/ /******************************************************************************/ @@ -32,14 +73,13 @@ // Abort execution by throwing if an unexpected condition arise. // - https://github.com/chrisaljoudi/uBlock/issues/456 -if ( typeof vAPI !== 'object' || vAPI.contentscriptInjected ) { +if ( typeof vAPI !== 'object' ) { throw new Error('uBlock Origin: aborting content scripts for ' + window.location); } +vAPI.lock(); vAPI.executionCost.start(); -vAPI.contentscriptInjected = true; - vAPI.matchesProp = (function() { var docElem = document.documentElement; if ( typeof docElem.matches !== 'function' ) { @@ -119,16 +159,16 @@ var jobQueue = [ var reParserEx = /:(?:matches-css|has|style|xpath)\(.+?\)$/; -var allExceptions = Object.create(null); -var allSelectors = Object.create(null); -var stagedNodes = []; -var matchesProp = vAPI.matchesProp; +var allExceptions = Object.create(null), + allSelectors = Object.create(null), + stagedNodes = [], + matchesProp = vAPI.matchesProp; // Complex selectors, due to their nature may need to be "de-committed". A // Set() is used to implement this functionality. -var complexSelectorsOldResultSet; -var complexSelectorsCurrentResultSet = new Set(); +var complexSelectorsOldResultSet, + complexSelectorsCurrentResultSet = new Set(); /******************************************************************************/ @@ -245,6 +285,7 @@ var domFilterer = { enabled: true, hiddenId: vAPI.randomToken(), hiddenNodeCount: 0, + loggerEnabled: undefined, styleTags: [], jobQueue: jobQueue, @@ -352,6 +393,10 @@ var domFilterer = { }, commit_: function() { + if ( stagedNodes.length === 0 ) { + return; + } + var beforeHiddenNodeCount = this.hiddenNodeCount, styleText = '', i, n; @@ -423,7 +468,11 @@ var domFilterer = { } // If DOM nodes have been affected, lazily notify core process. - if ( commitHit && cosmeticFiltersActivatedTimer === null ) { + if ( + this.loggerEnabled !== false && + commitHit && + cosmeticFiltersActivatedTimer === null + ) { cosmeticFiltersActivatedTimer = vAPI.setTimeout( cosmeticFiltersActivated, 503 @@ -512,6 +561,10 @@ var domFilterer = { } }, + toggleLogging: function(state) { + this.loggerEnabled = state; + }, + toggleOff: function() { this.enabled = false; }, @@ -583,89 +636,77 @@ return domFilterer; (function domIsLoading() { - // Domain-based ABP cosmetic filters. - // These can be inserted before the DOM is loaded. - - var cosmeticFilters = function(details) { - var domFilterer = vAPI.domFilterer; - domFilterer.addExceptions(details.cosmeticDonthide); - // https://github.com/chrisaljoudi/uBlock/issues/143 - domFilterer.addSelectors(details.cosmeticHide); - domFilterer.commit(undefined, true); - }; - - var netFilters = function(details) { - var parent = document.head || document.documentElement; - if ( !parent ) { + var responseHandler = function(response) { + // cosmetic filtering engine aka 'cfe' + var cfeDetails = response && response.specificCosmeticFilters; + if ( !cfeDetails || !cfeDetails.ready ) { + vAPI.domWatcher = vAPI.domCollapser = vAPI.domFilterer = + vAPI.domSurveyor = vAPI.domIsLoaded = null; + vAPI.unlock(); return; } - var styleTag = document.createElement('style'); - styleTag.setAttribute('type', 'text/css'); - var text = details.netHide.join(',\n'); - var css = details.netCollapse ? - '\n{display:none !important;}' : - '\n{visibility:hidden !important;}'; - styleTag.appendChild(document.createTextNode(text + css)); - parent.appendChild(styleTag); - }; - // Create script tags and assign data URIs looked up from our library of - // redirection resources: Sometimes it is useful to use these resources as - // standalone scriptlets. These scriptlets are injected from within the - // content scripts because what must be injected, if anything, depends on - // the currently active filters, as selected by the user. - // Library of redirection resources is located at: - // https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt - - var injectScripts = function(scripts) { - var parent = document.head || document.documentElement; - if ( !parent ) { - return; - } - var scriptTag = document.createElement('script'); - // Have the injected script tag remove itself when execution completes: - // to keep DOM as clean as possible. - scripts += - "\n" + - "(function() {\n" + - " var c = document.currentScript,\n" + - " p = c && c.parentNode;\n" + - " if ( p ) {\n" + - " p.removeChild(c);\n" + - " }\n" + - "})();"; - scriptTag.appendChild(document.createTextNode(scripts)); - parent.appendChild(scriptTag); - vAPI.injectedScripts = scripts; - }; - - var responseHandler = function(details) { vAPI.executionCost.start(); - if ( details ) { - vAPI.skipCosmeticFiltering = details.skipCosmeticFiltering; - vAPI.skipCosmeticSurveying = details.skipCosmeticSurveying; - if ( - (details.skipCosmeticFiltering !== true) && - (details.cosmeticHide.length !== 0 || details.cosmeticDonthide.length !== 0) - ) { - cosmeticFilters(details); + if ( response.noCosmeticFiltering ) { + vAPI.domFilterer = null; + vAPI.domSurveyor = null; + } else { + var domFilterer = vAPI.domFilterer; + domFilterer.toggleLogging(response.loggerEnabled); + if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) { + vAPI.domSurveyor = null; } - if ( details.netHide.length !== 0 ) { - netFilters(details); + if ( cfeDetails.cosmeticHide.length !== 0 || cfeDetails.cosmeticDonthide.length !== 0 ) { + domFilterer.addExceptions(cfeDetails.cosmeticDonthide); + domFilterer.addSelectors(cfeDetails.cosmeticHide); + domFilterer.commit(undefined, true); } - if ( details.scripts ) { - injectScripts(details.scripts); + } + + var parent = document.head || document.documentElement; + if ( parent ) { + var elem, text; + if ( cfeDetails.netHide.length !== 0 ) { + elem = document.createElement('style'); + elem.setAttribute('type', 'text/css'); + text = cfeDetails.netHide.join(',\n'); + text += response.collapseBlocked ? + '\n{display:none !important;}' : + '\n{visibility:hidden !important;}'; + elem.appendChild(document.createTextNode(text)); + parent.appendChild(elem); + } + // Library of resources is located at: + // https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt + if ( cfeDetails.scripts ) { + elem = document.createElement('script'); + // Have the injected script tag remove itself when execution completes: + // to keep DOM as clean as possible. + text = cfeDetails.scripts + + "\n" + + "(function() {\n" + + " var c = document.currentScript,\n" + + " p = c && c.parentNode;\n" + + " if ( p ) {\n" + + " p.removeChild(c);\n" + + " }\n" + + "})();"; + elem.appendChild(document.createTextNode(text)); + parent.appendChild(elem); + vAPI.injectedScripts = text; } - // The port will never be used again at this point, disconnecting - // allows the browser to flush this script from memory. } // 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 cleaned right after browser launch. - vAPI.contentscriptInjected = details && details.ready; + if ( document.readyState !== 'loading' ) { + window.requestAnimationFrame(vAPI.domIsLoaded); + } else { + document.addEventListener('DOMContentLoaded', vAPI.domIsLoaded); + } vAPI.executionCost.stop('domIsLoading/responseHandler'); }; @@ -674,7 +715,7 @@ return domFilterer; vAPI.messaging.send( 'contentscript', { - what: 'retrieveDomainCosmeticSelectors', + what: 'retrieveContentScriptParameters', pageURL: url, locationURL: url }, @@ -687,7 +728,137 @@ return domFilterer; /******************************************************************************/ /******************************************************************************/ -var domCollapser = (function() { +vAPI.domWatcher = (function() { + + var domLayoutObserver = null, + ignoreTags = { 'head': 1, 'link': 1, 'meta': 1, 'script': 1, 'style': 1 }, + addedNodeLists = [], + addedNodes = [], + removedNodes = false, + safeObserverHandlerTimer = null, + listeners = []; + + var safeObserverHandler = function() { + vAPI.executionCost.start(); + + safeObserverHandlerTimer = null; + var i = addedNodeLists.length, + nodeList, iNode, node; + while ( i-- ) { + nodeList = addedNodeLists[i]; + iNode = nodeList.length; + while ( iNode-- ) { + node = nodeList[iNode]; + if ( node.nodeType !== 1 ) { + continue; + } + if ( ignoreTags[node.localName] === 1 ) { + continue; + } + addedNodes.push(node); + } + } + addedNodeLists.length = 0; + if ( addedNodes.length !== 0 ) { + listeners[0](addedNodes, removedNodes); + if ( listeners[1] ) { + listeners[1](addedNodes, removedNodes); + } + addedNodes.length = 0; + removedNodes = false; + } + + vAPI.executionCost.stop('domWatcher/safeObserverHandler'); + }; + + // https://github.com/chrisaljoudi/uBlock/issues/205 + // Do not handle added node directly from within mutation observer. + var observerHandler = function(mutations) { + vAPI.executionCost.start(); + + var nodeList, mutation, + i = mutations.length; + while ( i-- ) { + mutation = mutations[i]; + nodeList = mutation.addedNodes; + if ( nodeList.length !== 0 ) { + addedNodeLists.push(nodeList); + } + if ( mutation.removedNodes.length !== 0 ) { + removedNodes = true; + } + } + if ( (addedNodeLists.length !== 0 || removedNodes) && safeObserverHandlerTimer === null ) { + safeObserverHandlerTimer = window.requestAnimationFrame(safeObserverHandler); + } + + vAPI.executionCost.stop('domWatcher/observerHandler'); + }; + + var addListener = function(listener) { + if ( listeners.indexOf(listener) !== -1 ) { + return; + } + listeners.push(listener); + if ( domLayoutObserver !== null ) { + return; + } + domLayoutObserver = new MutationObserver(observerHandler); + domLayoutObserver.observe(document.documentElement, { + //attributeFilter: [ 'class', 'id' ], + //attributes: true, + childList: true, + subtree: true + }); + }; + + var removeListener = function(listener) { + var pos = listeners.indexOf(listener); + if ( pos === -1 ) { + return; + } + listeners.splice(pos, 1); + if ( listeners.length !== 0 || domLayoutObserver === null ) { + return; + } + domLayoutObserver.disconnect(); + domLayoutObserver = null; + }; + + var stop = function() { + if ( domLayoutObserver !== null ) { + domLayoutObserver.disconnect(); + domLayoutObserver = null; + } + if ( safeObserverHandlerTimer !== null ) { + window.cancelAnimationFrame(safeObserverHandlerTimer); + } + }; + + var start = function() { + // Observe changes in the DOM only if... + // - there is a document.body + // - there is at least one `script` tag + if ( document.body === null || document.querySelector('script') === null ) { + vAPI.domWatcher = null; + return; + } + // https://github.com/gorhill/uMatrix/issues/144 + vAPI.shutdown.add(stop); + }; + + return { + addListener: addListener, + removeListener: removeListener, + start: start + }; +})(); + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +vAPI.domCollapser = (function() { var timer = null; var pendingRequests = Object.create(null); var roundtripRequests = []; @@ -913,18 +1084,64 @@ var domCollapser = (function() { } }; - var iframesFromNode = function(node) { - if ( node.localName === 'iframe' ) { - addIFrame(node); - process(); - } - if ( node.children.length !== 0 ) { - var iframes = node.getElementsByTagName('iframe'); - if ( iframes.length !== 0 ) { - addIFrames(iframes); - process(); + var domChangedHandler = function(nodes) { + var node; + for ( var i = 0, ni = nodes.length; i < ni; i++ ) { + node = nodes[i]; + if ( node.localName === 'iframe' ) { + addIFrame(node); + } + if ( node.children.length !== 0 ) { + var iframes = node.getElementsByTagName('iframe'); + if ( iframes.length !== 0 ) { + addIFrames(iframes); + } } } + process(); + }; + + var onResourceFailed = function(ev) { + vAPI.executionCost.start(); + vAPI.domCollapser.add(ev.target); + vAPI.domCollapser.process(); + vAPI.executionCost.stop('domIsLoaded/onResourceFailed'); + }; + + var start = function() { + // Listener to collapse blocked resources. + // - Future requests not blocked yet + // - Elements dynamically added to the page + // - Elements which resource URL changes + + // https://github.com/chrisaljoudi/uBlock/issues/7 + // Preferring getElementsByTagName over querySelectorAll: + // http://jsperf.com/queryselectorall-vs-getelementsbytagname/145 + var elems = document.images || document.getElementsByTagName('img'), + i = elems.length, elem; + while ( i-- ) { + elem = elems[i]; + if ( elem.complete ) { + add(elem); + } + } + addMany(document.embeds || document.getElementsByTagName('embed')); + addMany(document.getElementsByTagName('object')); + addIFrames(document.getElementsByTagName('iframe')); + process(0); + + document.addEventListener('error', onResourceFailed, true); + if ( vAPI.domWatcher ) { + vAPI.domWatcher.addListener(domChangedHandler); + } + + // https://github.com/gorhill/uMatrix/issues/144 + vAPI.shutdown.add(function() { + document.removeEventListener('error', onResourceFailed, true); + if ( vAPI.domWatcher ) { + vAPI.domWatcher.removeListener(domChangedHandler); + } + }); }; return { @@ -932,8 +1149,8 @@ var domCollapser = (function() { addMany: addMany, addIFrame: addIFrame, addIFrames: addIFrames, - iframesFromNode: iframesFromNode, - process: process + process: process, + start: start }; })(); @@ -941,601 +1158,418 @@ var domCollapser = (function() { /******************************************************************************/ /******************************************************************************/ -var domIsLoaded = function(ev) { - -/******************************************************************************/ - -if ( ev ) { - document.removeEventListener('DOMContentLoaded', domIsLoaded); -} - -// I've seen this happens on Firefox -if ( window.location === null ) { - return; -} - -// https://github.com/chrisaljoudi/uBlock/issues/587 -// Pointless to execute without the start script having done its job. -if ( !vAPI.contentscriptInjected ) { - return; -} - -vAPI.executionCost.start(); - -/******************************************************************************* - -skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit - skip-survey=true: commit - -*/ - -// Cosmetic filtering. - -(function() { - if ( vAPI.skipCosmeticFiltering ) { - //console.debug('Abort cosmetic filtering'); - return; - } - +vAPI.domSurveyor = (function() { // https://github.com/chrisaljoudi/uBlock/issues/789 // https://github.com/gorhill/uBlock/issues/873 // Be sure that our style tags used for cosmetic filtering are still // applied. - var domFilterer = vAPI.domFilterer; - domFilterer.checkStyleTags(false); - domFilterer.commit(); - var contextNodes = [ document.documentElement ], - messaging = vAPI.messaging; + var domFilterer = null, + messaging = vAPI.messaging, + surveyPhase3Nodes = [], + cosmeticSurveyingMissCount = 0, + highGenerics = null, + lowGenericSelectors = [], + queriedSelectors = Object.create(null), + removedNodesHandlerMissCount = 0; - var domSurveyor = (function() { - if ( vAPI.skipCosmeticSurveying === true ) { + // Handle main process' response. + + var surveyPhase3 = function(response) { + // https://github.com/gorhill/uMatrix/issues/144 + if ( response && response.shutdown ) { + vAPI.shutdown.exec(); return; } - var cosmeticSurveyingMissCount = 0, - highGenerics = null, - lowGenericSelectors = [], - queriedSelectors = Object.create(null); + vAPI.executionCost.start(); - // Handle main process' response. + var result = response && response.result, + firstSurvey = highGenerics === null; - var surveyPhase3 = function(response) { - // https://github.com/gorhill/uMatrix/issues/144 - if ( response && response.shutdown ) { - vAPI.shutdown.exec(); - return; + if ( result ) { + if ( result.hide.length ) { + processLowGenerics(result.hide); } - - vAPI.executionCost.start(); - - var result = response && response.result, - firstSurvey = highGenerics === null; - - if ( result ) { - if ( result.hide.length ) { - processLowGenerics(result.hide); - } - if ( result.highGenerics ) { - highGenerics = result.highGenerics; - } + if ( result.highGenerics ) { + highGenerics = result.highGenerics; } + } - if ( highGenerics ) { - if ( highGenerics.hideLowCount ) { - processHighLowGenerics(highGenerics.hideLow); - } - if ( highGenerics.hideMediumCount ) { - processHighMediumGenerics(highGenerics.hideMedium); - } - if ( highGenerics.hideHighSimpleCount || highGenerics.hideHighComplexCount ) { - processHighHighGenerics(); - } + if ( highGenerics ) { + if ( highGenerics.hideLowCount ) { + processHighLowGenerics(highGenerics.hideLow); } - - // Need to do this before committing DOM filterer, as needed info - // will no longer be there after commit. - if ( firstSurvey || domFilterer.job0._0.length ) { - messaging.send( - 'contentscript', - { - what: 'cosmeticFiltersInjected', - type: 'cosmetic', - hostname: window.location.hostname, - selectors: domFilterer.job0._0 - } - ); + if ( highGenerics.hideMediumCount ) { + processHighMediumGenerics(highGenerics.hideMedium); } + if ( highGenerics.hideHighSimpleCount || highGenerics.hideHighComplexCount ) { + processHighHighGenerics(); + } + } - // Shutdown surveyor if too many consecutive empty resultsets. - if ( domFilterer.job0._0.length === 0 ) { - cosmeticSurveyingMissCount += 1; - if ( cosmeticSurveyingMissCount > 255 ) { - domSurveyor = undefined; + // Need to do this before committing DOM filterer, as needed info + // will no longer be there after commit. + if ( firstSurvey || domFilterer.job0._0.length ) { + messaging.send( + 'contentscript', + { + what: 'cosmeticFiltersInjected', + type: 'cosmetic', + hostname: window.location.hostname, + selectors: domFilterer.job0._0 } - } else { - cosmeticSurveyingMissCount = 0; - } + ); + } - domFilterer.commit(contextNodes); - contextNodes = []; + // Shutdown surveyor if too many consecutive empty resultsets. + if ( domFilterer.job0._0.length === 0 ) { + cosmeticSurveyingMissCount += 1; + } else { + cosmeticSurveyingMissCount = 0; + } - vAPI.executionCost.stop('domIsLoaded/surveyPhase2'); - }; + domFilterer.commit(surveyPhase3Nodes); + surveyPhase3Nodes = []; - // Query main process. + vAPI.executionCost.stop('domIsLoaded/surveyPhase2'); + }; - var surveyPhase2 = function() { - if ( lowGenericSelectors.length !== 0 || highGenerics === null ) { - messaging.send( - 'contentscript', - { - what: 'retrieveGenericCosmeticSelectors', - pageURL: window.location.href, - selectors: lowGenericSelectors, - firstSurvey: highGenerics === null - }, - surveyPhase3 - ); - lowGenericSelectors = []; - } else { - surveyPhase3(null); - } - }; + // Query main process. - // Low generics: - // - [id] - // - [class] + var surveyPhase2 = function(addedNodes) { + surveyPhase3Nodes = surveyPhase3Nodes.concat(addedNodes); + if ( lowGenericSelectors.length !== 0 || highGenerics === null ) { + messaging.send( + 'contentscript', + { + what: 'retrieveGenericCosmeticSelectors', + pageURL: window.location.href, + selectors: lowGenericSelectors, + firstSurvey: highGenerics === null + }, + surveyPhase3 + ); + lowGenericSelectors = []; + } else { + surveyPhase3(null); + } + }; - var processLowGenerics = function(generics) { - domFilterer.addSelectors(generics); - }; + // Low generics: + // - [id] + // - [class] - // High-low generics: - // - [alt="..."] - // - [title="..."] + var processLowGenerics = function(generics) { + domFilterer.addSelectors(generics); + }; - var processHighLowGenerics = function(generics) { - var attrs = ['title', 'alt']; - var attr, attrValue, nodeList, iNode, node; - var selector; - while ( (attr = attrs.pop()) ) { - nodeList = selectNodes('[' + attr + ']'); - iNode = nodeList.length; - while ( iNode-- ) { - node = nodeList[iNode]; - attrValue = node.getAttribute(attr); - if ( !attrValue ) { continue; } - // Candidate 1 = generic form - // If generic form is injected, no need to process the - // specific form, as the generic will affect all related - // specific forms. - selector = '[' + attr + '="' + attrValue + '"]'; - if ( generics.hasOwnProperty(selector) ) { - domFilterer.addSelector(selector); - continue; - } - // Candidate 2 = specific form - selector = node.localName + selector; - if ( generics.hasOwnProperty(selector) ) { - domFilterer.addSelector(selector); - } - } - } - }; + // High-low generics: + // - [alt="..."] + // - [title="..."] - // High-medium generics: - // - [href^="http"] - - var processHighMediumGenerics = function(generics) { - var stagedNodes = contextNodes, - i = stagedNodes.length; - if ( i === 1 && stagedNodes[0] === document.documentElement ) { - processHighMediumGenericsForNodes(document.links, generics); - return; - } - var aa = [ null ], - node, nodes; - while ( i-- ) { - node = stagedNodes[i]; - if ( node.localName === 'a' ) { - aa[0] = node; - processHighMediumGenericsForNodes(aa, generics); - } - nodes = node.getElementsByTagName('a'); - if ( nodes.length !== 0 ) { - processHighMediumGenericsForNodes(nodes, generics); - } - } - }; - - var processHighMediumGenericsForNodes = function(nodes, generics) { - var i = nodes.length, - node, href, pos, entry, j, selector; - while ( i-- ) { - node = nodes[i]; - href = node.getAttribute('href'); - if ( !href ) { continue; } - pos = href.indexOf('://'); - if ( pos === -1 ) { continue; } - entry = generics[href.slice(pos + 3, pos + 11)]; - if ( entry === undefined ) { continue; } - if ( typeof entry === 'string' ) { - if ( href.lastIndexOf(entry.slice(8, -2), 0) === 0 ) { - domFilterer.addSelector(entry); - } + var processHighLowGenerics = function(generics) { + var attrs = ['title', 'alt']; + var attr, attrValue, nodeList, iNode, node; + var selector; + while ( (attr = attrs.pop()) ) { + nodeList = selectNodes('[' + attr + ']', surveyPhase3Nodes); + iNode = nodeList.length; + while ( iNode-- ) { + node = nodeList[iNode]; + attrValue = node.getAttribute(attr); + if ( !attrValue ) { continue; } + // Candidate 1 = generic form + // If generic form is injected, no need to process the + // specific form, as the generic will affect all related + // specific forms. + selector = '[' + attr + '="' + attrValue + '"]'; + if ( generics.hasOwnProperty(selector) ) { + domFilterer.addSelector(selector); continue; } - j = entry.length; - while ( j-- ) { - selector = entry[j]; - if ( href.lastIndexOf(selector.slice(8, -2), 0) === 0 ) { - domFilterer.addSelector(selector); - } + // Candidate 2 = specific form + selector = node.localName + selector; + if ( generics.hasOwnProperty(selector) ) { + domFilterer.addSelector(selector); } } - }; + } + }; - var highHighSimpleGenericsCost = 0, - highHighSimpleGenericsInjected = false, - highHighComplexGenericsCost = 0, - highHighComplexGenericsInjected = false; + // High-medium generics: + // - [href^="http"] - var processHighHighGenerics = function() { - var tstart; - // Simple selectors. - if ( - highHighSimpleGenericsInjected === false && - highHighSimpleGenericsCost < 50 && - highGenerics.hideHighSimpleCount !== 0 - ) { - tstart = window.performance.now(); - var matchesProp = vAPI.matchesProp, - nodes = contextNodes, - i = nodes.length, node; - while ( i-- ) { - node = nodes[i]; - if ( - node[matchesProp](highGenerics.hideHighSimple) || - node.querySelector(highGenerics.hideHighSimple) !== null - ) { - highHighSimpleGenericsInjected = true; - domFilterer.addSelectors(highGenerics.hideHighSimple.split(',\n')); - break; - } + var processHighMediumGenerics = function(generics) { + var stagedNodes = surveyPhase3Nodes, + i = stagedNodes.length; + if ( i === 1 && stagedNodes[0] === document.documentElement ) { + processHighMediumGenericsForNodes(document.links, generics); + return; + } + var aa = [ null ], + node, nodes; + while ( i-- ) { + node = stagedNodes[i]; + if ( node.localName === 'a' ) { + aa[0] = node; + processHighMediumGenericsForNodes(aa, generics); + } + nodes = node.getElementsByTagName('a'); + if ( nodes.length !== 0 ) { + processHighMediumGenericsForNodes(nodes, generics); + } + } + }; + + var processHighMediumGenericsForNodes = function(nodes, generics) { + var i = nodes.length, + node, href, pos, entry, j, selector; + while ( i-- ) { + node = nodes[i]; + href = node.getAttribute('href'); + if ( !href ) { continue; } + pos = href.indexOf('://'); + if ( pos === -1 ) { continue; } + entry = generics[href.slice(pos + 3, pos + 11)]; + if ( entry === undefined ) { continue; } + if ( typeof entry === 'string' ) { + if ( href.lastIndexOf(entry.slice(8, -2), 0) === 0 ) { + domFilterer.addSelector(entry); } - highHighSimpleGenericsCost += window.performance.now() - tstart; + continue; } - // Complex selectors. - if ( - highHighComplexGenericsInjected === false && - highHighComplexGenericsCost < 50 && - highGenerics.hideHighComplexCount !== 0 - ) { - tstart = window.performance.now(); - if ( document.querySelector(highGenerics.hideHighComplex) !== null ) { - highHighComplexGenericsInjected = true; - domFilterer.addSelectors(highGenerics.hideHighComplex.split(',\n')); - domFilterer.commit(); - } - highHighComplexGenericsCost += window.performance.now() - tstart; - } - }; - - // Extract and return the staged nodes which (may) match the selectors. - - var selectNodes = function(selector) { - var stagedNodes = contextNodes, - i = stagedNodes.length; - if ( i === 1 && stagedNodes[0] === document.documentElement ) { - return document.querySelectorAll(selector); - } - var targetNodes = [], - node, nodeList, j; - while ( i-- ) { - node = stagedNodes[i]; - targetNodes.push(node); - nodeList = node.querySelectorAll(selector); - j = nodeList.length; - while ( j-- ) { - targetNodes.push(nodeList[j]); + j = entry.length; + while ( j-- ) { + selector = entry[j]; + if ( href.lastIndexOf(selector.slice(8, -2), 0) === 0 ) { + domFilterer.addSelector(selector); } } - return targetNodes; - }; + } + }; - // Extract all classes/ids: these will be passed to the cosmetic - // filtering engine, and in return we will obtain only the relevant - // CSS selectors. + var highHighSimpleGenericsCost = 0, + highHighSimpleGenericsInjected = false, + highHighComplexGenericsCost = 0, + highHighComplexGenericsInjected = false; - // https://github.com/gorhill/uBlock/issues/672 - // http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens - // http://jsperf.com/enumerate-classes/6 - - var surveyPhase1 = function() { - var nodes = selectNodes('[class],[id]'); - var qq = queriedSelectors; - var ll = lowGenericSelectors; - var node, v, vv, j; - var i = nodes.length; + var processHighHighGenerics = function() { + var tstart; + // Simple selectors. + if ( + highHighSimpleGenericsInjected === false && + highHighSimpleGenericsCost < 50 && + highGenerics.hideHighSimpleCount !== 0 + ) { + tstart = window.performance.now(); + var matchesProp = vAPI.matchesProp, + nodes = surveyPhase3Nodes, + i = nodes.length, node; while ( i-- ) { node = nodes[i]; - if ( node.nodeType !== 1 ) { continue; } - v = node.id; - if ( v !== '' && typeof v === 'string' ) { - v = '#' + v.trim(); - if ( v !== '#' && qq[v] === undefined ) { - ll.push(v); - qq[v] = true; - } + if ( + node[matchesProp](highGenerics.hideHighSimple) || + node.querySelector(highGenerics.hideHighSimple) !== null + ) { + highHighSimpleGenericsInjected = true; + domFilterer.addSelectors(highGenerics.hideHighSimple.split(',\n')); + break; } - vv = node.className; - if ( vv === '' || typeof vv !== 'string' ) { continue; } - if ( /\s/.test(vv) === false ) { - v = '.' + vv; + } + highHighSimpleGenericsCost += window.performance.now() - tstart; + } + // Complex selectors. + if ( + highHighComplexGenericsInjected === false && + highHighComplexGenericsCost < 50 && + highGenerics.hideHighComplexCount !== 0 + ) { + tstart = window.performance.now(); + if ( document.querySelector(highGenerics.hideHighComplex) !== null ) { + highHighComplexGenericsInjected = true; + domFilterer.addSelectors(highGenerics.hideHighComplex.split(',\n')); + } + highHighComplexGenericsCost += window.performance.now() - tstart; + } + }; + + // Extract and return the staged nodes which (may) match the selectors. + + var selectNodes = function(selector, nodes) { + var stagedNodes = nodes, + i = stagedNodes.length; + if ( i === 1 && stagedNodes[0] === document.documentElement ) { + return document.querySelectorAll(selector); + } + var targetNodes = [], + node, nodeList, j; + while ( i-- ) { + node = stagedNodes[i]; + targetNodes.push(node); + nodeList = node.querySelectorAll(selector); + j = nodeList.length; + while ( j-- ) { + targetNodes.push(nodeList[j]); + } + } + return targetNodes; + }; + + // Extract all classes/ids: these will be passed to the cosmetic + // filtering engine, and in return we will obtain only the relevant + // CSS selectors. + + // https://github.com/gorhill/uBlock/issues/672 + // http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens + // http://jsperf.com/enumerate-classes/6 + + var surveyPhase1 = function(addedNodes) { + var nodes = selectNodes('[class],[id]', addedNodes); + var qq = queriedSelectors; + var ll = lowGenericSelectors; + var node, v, vv, j; + var i = nodes.length; + while ( i-- ) { + node = nodes[i]; + if ( node.nodeType !== 1 ) { continue; } + v = node.id; + if ( v !== '' && typeof v === 'string' ) { + v = '#' + v.trim(); + if ( v !== '#' && qq[v] === undefined ) { + ll.push(v); + qq[v] = true; + } + } + vv = node.className; + if ( vv === '' || typeof vv !== 'string' ) { continue; } + if ( /\s/.test(vv) === false ) { + v = '.' + vv; + if ( qq[v] === undefined ) { + ll.push(v); + qq[v] = true; + } + } else { + vv = node.classList; + j = vv.length; + while ( j-- ) { + v = '.' + vv[j]; if ( qq[v] === undefined ) { ll.push(v); qq[v] = true; } - } else { - vv = node.classList; - j = vv.length; - while ( j-- ) { - v = '.' + vv[j]; - if ( qq[v] === undefined ) { - ll.push(v); - qq[v] = true; - } - } } } - surveyPhase2(); + } + surveyPhase2(addedNodes); + }; + + var domChangedHandler = function(addedNodes, removedNodes) { + if ( cosmeticSurveyingMissCount < 256 ) { + surveyPhase1(addedNodes); + } else { + domFilterer.commit(addedNodes); + } + // https://github.com/gorhill/uBlock/issues/873 + // This will ensure our style elements will stay in the DOM. + if ( removedNodes && removedNodesHandlerMissCount < 16 ) { + if ( domFilterer.checkStyleTags(true) === false ) { + removedNodesHandlerMissCount += 1; + } + } + }; + + var start = function() { + domFilterer = vAPI.domFilterer; + if ( domFilterer === null ) { + return; + } + domFilterer.checkStyleTags(false); + domFilterer.commit(); + domChangedHandler([ document.documentElement ]); + if ( vAPI.domWatcher ) { + vAPI.domWatcher.addListener(domChangedHandler); + } + }; + + return { + start: start + }; +})(); + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +vAPI.domIsLoaded = function(ev) { + if ( ev ) { + document.removeEventListener('DOMContentLoaded', vAPI.domIsLoaded); + } + vAPI.domIsLoaded = null; + + // I've seen this happens on Firefox + if ( window.location === null ) { + return; + } + + vAPI.executionCost.start(); + + if ( vAPI.domWatcher ) { + vAPI.domWatcher.start(); + } + + if ( vAPI.domCollapser ) { + vAPI.domCollapser.start(); + } + + if ( vAPI.domFilterer && vAPI.domSurveyor ) { + vAPI.domSurveyor.start(); + } + + // To send mouse coordinates to main process, as the chrome API fails + // to provide the mouse position to context menu listeners. + // https://github.com/chrisaljoudi/uBlock/issues/1143 + // Also, find a link under the mouse, to try to avoid confusing new tabs + // as nuisance popups. + // Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu + + (function() { + if ( window !== window.top || !vAPI.domFilterer ) { + return; + } + + var messaging = vAPI.messaging; + + var onMouseClick = function(ev) { + vAPI.executionCost.start(); + var elem = ev.target; + while ( elem !== null && elem.localName !== 'a' ) { + elem = elem.parentElement; + } + messaging.send( + 'contentscript', + { + what: 'mouseClick', + x: ev.clientX, + y: ev.clientY, + url: elem !== null ? elem.href : '' + }); + vAPI.executionCost.stop('domIsLoaded/onMouseClick'); }; - return surveyPhase1; + document.addEventListener('mousedown', onMouseClick, true); + + // https://github.com/gorhill/uMatrix/issues/144 + vAPI.shutdown.add(function() { + document.removeEventListener('mousedown', onMouseClick, true); + }); })(); - // Start cosmetic filtering. - - if ( domSurveyor ) { - domSurveyor(); - } - - //console.debug('%f: uBlock: survey time', timer.now() - tStart); - - // Below this point is the code which takes care to observe changes in - // the page and to add if needed relevant CSS rules as a result of the - // changes. - - // Observe changes in the DOM only if... - // - there is a document.body - // - there is at least one `script` tag - if ( !document.body || !document.querySelector('script') ) { - return; - } - - // https://github.com/chrisaljoudi/uBlock/issues/618 - // Following is to observe dynamically added iframes: - // - On Firefox, the iframes fails to fire a `load` event - - var ignoreTags = { - 'link': true, - 'meta': true, - 'script': true, - 'style': true - }; - - // Added node lists will be cumulated here before being processed - var addedNodeLists = [], - addedNodeListsTimer = null, - removedNodeListsTimer = null, - removedNodesHandlerMissCount = 0, - collapser = domCollapser; - - var addedNodesHandler = function() { - vAPI.executionCost.start(); - - addedNodeListsTimer = null; - var iNodeList = addedNodeLists.length, - nodeList, iNode, node; - while ( iNodeList-- ) { - nodeList = addedNodeLists[iNodeList]; - iNode = nodeList.length; - while ( iNode-- ) { - node = nodeList[iNode]; - if ( node.nodeType !== 1 ) { - continue; - } - if ( ignoreTags.hasOwnProperty(node.localName) ) { - continue; - } - contextNodes.push(node); - collapser.iframesFromNode(node); - } - } - addedNodeLists.length = 0; - if ( contextNodes.length !== 0 ) { - if ( domSurveyor ) { - domSurveyor(); - } else { - domFilterer.commit(contextNodes); - contextNodes = []; - if ( domFilterer.commitMissCount > 255 ) { - domLayoutObserver.disconnect(); - } - } - } - - vAPI.executionCost.stop('domIsLoaded/addedNodesHandler'); - }; - - // https://github.com/gorhill/uBlock/issues/873 - // This will ensure our style elements will stay in the DOM. - var removedNodesHandler = function() { - if ( domFilterer.checkStyleTags(true) === false ) { - removedNodesHandlerMissCount += 1; - } - if ( removedNodesHandlerMissCount < 16 ) { - removedNodeListsTimer = null; - } - }; - - // https://github.com/chrisaljoudi/uBlock/issues/205 - // Do not handle added node directly from within mutation observer. - // I arbitrarily chose 100 ms for now: I have to compromise between the - // overhead of processing too few nodes too often and the delay of many - // nodes less often. - var domLayoutChanged = function(mutations) { - vAPI.executionCost.start(); - - var removedNodeLists = false; - var iMutation = mutations.length; - var nodeList, mutation; - while ( iMutation-- ) { - mutation = mutations[iMutation]; - nodeList = mutation.addedNodes; - if ( nodeList.length !== 0 ) { - addedNodeLists.push(nodeList); - } - if ( mutation.removedNodes.length !== 0 ) { - removedNodeLists = true; - } - } - if ( addedNodeLists.length !== 0 && addedNodeListsTimer === null ) { - addedNodeListsTimer = window.requestAnimationFrame(addedNodesHandler); - } - if ( removedNodeLists && removedNodeListsTimer === null ) { - removedNodeListsTimer = window.requestAnimationFrame(removedNodesHandler); - } - - vAPI.executionCost.stop('domIsLoaded/domLayoutChanged'); - }; - - //console.debug('Starts cosmetic filtering\'s mutations observer'); - - // https://github.com/gorhill/httpswitchboard/issues/176 - var domLayoutObserver = new MutationObserver(domLayoutChanged); - domLayoutObserver.observe(document.documentElement, { - childList: true, - subtree: true - }); - - // https://github.com/gorhill/uMatrix/issues/144 - vAPI.shutdown.add(function() { - domLayoutObserver.disconnect(); - if ( addedNodeListsTimer !== null ) { - window.cancelAnimationFrame(addedNodeListsTimer); - } - if ( removedNodeListsTimer !== null ) { - window.cancelAnimationFrame(removedNodeListsTimer); - } - }); -})(); - -/******************************************************************************/ - -// Permanent - -// Listener to collapse blocked resources. -// - Future requests not blocked yet -// - Elements dynamically added to the page -// - Elements which resource URL changes - -(function() { - var onResourceFailed = function(ev) { - vAPI.executionCost.start(); - domCollapser.add(ev.target); - domCollapser.process(); - vAPI.executionCost.stop('domIsLoaded/onResourceFailed'); - }; - document.addEventListener('error', onResourceFailed, true); - - // https://github.com/gorhill/uMatrix/issues/144 - vAPI.shutdown.add(function() { - document.removeEventListener('error', onResourceFailed, true); - }); -})(); - -/******************************************************************************/ - -// https://github.com/chrisaljoudi/uBlock/issues/7 -// Executed only once. -// Preferring getElementsByTagName over querySelectorAll: -// http://jsperf.com/queryselectorall-vs-getelementsbytagname/145 - -(function() { - var collapser = domCollapser, - elems = document.images || document.getElementsByTagName('img'), - i = elems.length, elem; - while ( i-- ) { - elem = elems[i]; - if ( elem.complete ) { - collapser.add(elem); - } - } - collapser.addMany(document.embeds || document.getElementsByTagName('embed')); - collapser.addMany(document.getElementsByTagName('object')); - collapser.addIFrames(document.getElementsByTagName('iframe')); - collapser.process(0); -})(); - -/******************************************************************************/ - -// To send mouse coordinates to main process, as the chrome API fails -// to provide the mouse position to context menu listeners. - -// https://github.com/chrisaljoudi/uBlock/issues/1143 -// Also, find a link under the mouse, to try to avoid confusing new tabs -// as nuisance popups. - -// Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu - -(function() { - if ( window !== window.top ) { - return; - } - - var messaging = vAPI.messaging; - - var onMouseClick = function(ev) { - vAPI.executionCost.start(); - var elem = ev.target; - while ( elem !== null && elem.localName !== 'a' ) { - elem = elem.parentElement; - } - messaging.send( - 'contentscript', - { - what: 'mouseClick', - x: ev.clientX, - y: ev.clientY, - url: elem !== null ? elem.href : '' - }); - vAPI.executionCost.stop('domIsLoaded/onMouseClick'); - }; - - document.addEventListener('mousedown', onMouseClick, true); - - // https://github.com/gorhill/uMatrix/issues/144 - vAPI.shutdown.add(function() { - document.removeEventListener('mousedown', onMouseClick, true); - }); -})(); - -/******************************************************************************/ - -vAPI.executionCost.stop('domIsLoaded'); - + vAPI.executionCost.stop('domIsLoaded'); }; /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ -if ( document.readyState !== 'loading' ) { - window.requestAnimationFrame(domIsLoaded); -} else { - document.addEventListener('DOMContentLoaded', domIsLoaded); -} - vAPI.executionCost.stop('contentscript.js'); diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 358e9a325..598bfa65f 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -1550,16 +1550,17 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) { /******************************************************************************/ -FilterContainer.prototype.retrieveDomainSelectors = function(request) { +FilterContainer.prototype.retrieveDomainSelectors = function(request, noCosmeticFiltering) { if ( !request.locationURL ) { return; } //quickProfiler.start('FilterContainer.retrieve()'); - var hostname = this.µburi.hostnameFromURI(request.locationURL); - var domain = this.µburi.domainFromHostname(hostname) || hostname; - var pos = domain.indexOf('.'); + var hostname = this.µburi.hostnameFromURI(request.locationURL), + domain = this.µburi.domainFromHostname(hostname) || hostname, + pos = domain.indexOf('.'), + cacheEntry = this.selectorCache[hostname]; // https://github.com/chrisaljoudi/uBlock/issues/587 // r.ready will tell the content script the cosmetic filtering engine is @@ -1572,58 +1573,59 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) { ready: this.frozen, domain: domain, entity: pos === -1 ? domain : domain.slice(0, pos - domain.length), - skipCosmeticSurveying: false, - skipCosmeticFiltering: this.acceptedCount === 0, + noDOMSurveying: false, cosmeticHide: [], - cosmeticDonthide: this.genericDonthide.slice(), + cosmeticDonthide: [], netHide: [], - netCollapse: µb.userSettings.collapseBlocked, scripts: this.retrieveScriptTags(domain, hostname) }; - var hash, bucket; - hash = makeHash(0, domain, this.domainHashMask); - if ( (bucket = this.hostnameFilters[hash]) ) { - bucket.retrieve(hostname, r.cosmeticHide); - } - // https://github.com/chrisaljoudi/uBlock/issues/188 - // Special bucket for those filters without a valid domain name as per PSL - if ( (bucket = this.hostnameFilters[this.type0NoDomainHash]) ) { - bucket.retrieve(hostname, r.cosmeticHide); + if ( !noCosmeticFiltering ) { + var hash, bucket; + hash = makeHash(0, domain, this.domainHashMask); + if ( (bucket = this.hostnameFilters[hash]) ) { + bucket.retrieve(hostname, r.cosmeticHide); + } + // https://github.com/chrisaljoudi/uBlock/issues/188 + // Special bucket for those filters without a valid domain name as per PSL + if ( (bucket = this.hostnameFilters[this.type0NoDomainHash]) ) { + bucket.retrieve(hostname, r.cosmeticHide); + } + + // entity filter buckets are always plain js array + if ( this.entityFilters.hasOwnProperty(r.entity) ) { + r.cosmeticHide = r.cosmeticHide.concat(this.entityFilters[r.entity]); + } + + // cached cosmetic filters. + if ( cacheEntry ) { + cacheEntry.retrieve('cosmetic', r.cosmeticHide); + r.noDOMSurveying = cacheEntry.cosmeticSurveyingMissCount > cosmeticSurveyingMissCountMax; + } + + // Exception cosmetic filters. + r.cosmeticDonthide = this.genericDonthide.slice(); + + hash = makeHash(1, domain, this.domainHashMask); + if ( (bucket = this.hostnameFilters[hash]) ) { + bucket.retrieve(hostname, r.cosmeticDonthide); + } + + // https://github.com/chrisaljoudi/uBlock/issues/188 + // Special bucket for those filters without a valid domain name as per PSL + if ( (bucket = this.hostnameFilters[this.type1NoDomainHash]) ) { + bucket.retrieve(hostname, r.cosmeticDonthide); + } + // No entity exceptions as of now } - // entity filter buckets are always plain js array - if ( this.entityFilters.hasOwnProperty(r.entity) ) { - r.cosmeticHide = r.cosmeticHide.concat(this.entityFilters[r.entity]); - } - // No entity exceptions as of now - - hash = makeHash(1, domain, this.domainHashMask); - if ( (bucket = this.hostnameFilters[hash]) ) { - bucket.retrieve(hostname, r.cosmeticDonthide); - } - - // https://github.com/chrisaljoudi/uBlock/issues/188 - // Special bucket for those filters without a valid domain name as per PSL - if ( (bucket = this.hostnameFilters[this.type1NoDomainHash]) ) { - bucket.retrieve(hostname, r.cosmeticDonthide); - } - - var cacheEntry = this.selectorCache[hostname]; + // Collapsible blocked resources. if ( cacheEntry ) { - cacheEntry.retrieve('cosmetic', r.cosmeticHide); cacheEntry.retrieve('net', r.netHide); - r.skipCosmeticSurveying = cacheEntry.cosmeticSurveyingMissCount > cosmeticSurveyingMissCountMax; } //quickProfiler.stop(); - //console.log( - // 'µBlock> abp-hide-filters.js: "%s" => %d selectors out', - // request.locationURL, - // r.cosmeticHide.length + r.cosmeticDonthide.length - //); - return r; }; diff --git a/src/js/messaging.js b/src/js/messaging.js index 5229f4c59..dc5c9c011 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -518,12 +518,18 @@ var onMessage = function(request, sender, callback) { } switch ( request.what ) { - case 'retrieveDomainCosmeticSelectors': + case 'retrieveContentScriptParameters': if ( pageStore && pageStore.getNetFilteringSwitch() ) { - response = µb.cosmeticFilteringEngine.retrieveDomainSelectors(request); - if ( response && response.skipCosmeticFiltering !== true ) { - response.skipCosmeticFiltering = !pageStore.getSpecificCosmeticFilteringSwitch(); - } + response = { + loggerEnabled: µb.logger.isEnabled(), + collapseBlocked: µb.userSettings.collapseBlocked, + noCosmeticFiltering: µb.cosmeticFilteringEngine.acceptedCount === 0 || pageStore.noCosmeticFiltering === true, + noGenericCosmeticFiltering: pageStore.noGenericCosmeticFiltering === true + }; + response.specificCosmeticFilters = µb.cosmeticFilteringEngine.retrieveDomainSelectors( + request, + response.noCosmeticFiltering + ); } break; diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 361bfdd30..a0bb640b3 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -310,26 +310,42 @@ PageStore.prototype.init = function(tabId) { this.largeMediaTimer = null; this.netFilteringCache = NetFilteringResultCache.factory(); - // Support `elemhide` filter option. Called at this point so the required - // context is all setup at this point. - this.skipCosmeticFiltering = µb.staticNetFilteringEngine.matchStringExactType( - this.createContextFromPage(), - tabContext.normalURL, - 'elemhide' - ) === false; - if ( this.skipCosmeticFiltering && µb.logger.isEnabled() ) { - // https://github.com/gorhill/uBlock/issues/370 - // Log using `cosmetic-filtering`, not `elemhide`. + this.noCosmeticFiltering = µb.hnSwitches.evaluateZ('no-cosmetic-filtering', tabContext.rootHostname) === true; + if ( µb.logger.isEnabled() && this.noCosmeticFiltering ) { µb.logger.writeOne( tabId, - 'net', - µb.staticNetFilteringEngine.toResultString(true), - 'elemhide', + 'cosmetic', + µb.hnSwitches.toResultString(), + 'dom', tabContext.rawURL, this.tabHostname, this.tabHostname ); } + + // Support `generichide` filter option. + this.noGenericCosmeticFiltering = this.noCosmeticFiltering; + if ( this.noGenericCosmeticFiltering !== true ) { + this.noGenericCosmeticFiltering = µb.staticNetFilteringEngine.matchStringExactType( + this.createContextFromPage(), + tabContext.normalURL, + 'elemhide' + ) === false; + if ( µb.logger.isEnabled() && this.noGenericCosmeticFiltering ) { + // https://github.com/gorhill/uBlock/issues/370 + // Log using `cosmetic-filtering`, not `elemhide`. + µb.logger.writeOne( + tabId, + 'net', + µb.staticNetFilteringEngine.toResultString(true), + 'elemhide', + tabContext.rawURL, + this.tabHostname, + this.tabHostname + ); + } + } + return this; }; @@ -458,16 +474,14 @@ PageStore.prototype.getNetFilteringSwitch = function() { /******************************************************************************/ PageStore.prototype.getSpecificCosmeticFilteringSwitch = function() { - var tabContext = µb.tabContextManager.lookup(this.tabId); - return tabContext !== null && - µb.hnSwitches.evaluateZ('no-cosmetic-filtering', tabContext.rootHostname) !== true; + return this.noCosmeticFiltering !== true; }; /******************************************************************************/ PageStore.prototype.getGenericCosmeticFilteringSwitch = function() { - return this.skipCosmeticFiltering !== true && - this.getSpecificCosmeticFilteringSwitch(); + return this.noGenericCosmeticFiltering !== true && + this.noCosmeticFiltering !== true; }; /******************************************************************************/ diff --git a/src/js/scriptlets/cosmetic-logger.js b/src/js/scriptlets/cosmetic-logger.js index 4bd561a53..29ac23477 100644 --- a/src/js/scriptlets/cosmetic-logger.js +++ b/src/js/scriptlets/cosmetic-logger.js @@ -27,7 +27,7 @@ /******************************************************************************/ -if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { +if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; } diff --git a/src/js/scriptlets/cosmetic-off.js b/src/js/scriptlets/cosmetic-off.js index 02557e8be..8fdac3619 100644 --- a/src/js/scriptlets/cosmetic-off.js +++ b/src/js/scriptlets/cosmetic-off.js @@ -24,7 +24,7 @@ /******************************************************************************/ (function() { - if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { + if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; } diff --git a/src/js/scriptlets/cosmetic-on.js b/src/js/scriptlets/cosmetic-on.js index 3a5eea1a0..1a450e074 100644 --- a/src/js/scriptlets/cosmetic-on.js +++ b/src/js/scriptlets/cosmetic-on.js @@ -24,7 +24,7 @@ /******************************************************************************/ (function() { - if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { + if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; } diff --git a/src/js/scriptlets/cosmetic-survey.js b/src/js/scriptlets/cosmetic-survey.js index bcc1252fe..d3956ca70 100644 --- a/src/js/scriptlets/cosmetic-survey.js +++ b/src/js/scriptlets/cosmetic-survey.js @@ -24,7 +24,7 @@ /******************************************************************************/ (function() { - if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { + if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; } diff --git a/src/js/scriptlets/dom-inspector.js b/src/js/scriptlets/dom-inspector.js index 1bb086dae..50c8391c7 100644 --- a/src/js/scriptlets/dom-inspector.js +++ b/src/js/scriptlets/dom-inspector.js @@ -28,7 +28,7 @@ /******************************************************************************/ -if ( typeof vAPI !== 'object' || typeof vAPI.domFilterer !== 'object' ) { +if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; }