From 62a6b5251e3f5843cc6fed4f3c5dc8d3d4061781 Mon Sep 17 00:00:00 2001 From: gorhill Date: Sat, 31 Oct 2015 00:55:10 -0400 Subject: [PATCH] this fixes #873 --- src/js/contentscript-end.js | 109 +++++++++++++++++++--------------- src/js/contentscript-start.js | 2 +- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/src/js/contentscript-end.js b/src/js/contentscript-end.js index f2986b893..7c79a1031 100644 --- a/src/js/contentscript-end.js +++ b/src/js/contentscript-end.js @@ -81,30 +81,6 @@ vAPI.shutdown.add(function() { /******************************************************************************/ /******************************************************************************/ -// https://github.com/chrisaljoudi/uBlock/issues/789 -// Be sure that specific cosmetic filters are still applied. -// Executed once, then flushed from memory. - -(function() { - // Were there specific cosmetic filters? - if ( typeof vAPI.specificHideStyle !== 'object' ) { - return; - } - // Is our style tag still in the DOM? (the guess is whatever parent there - // is, it is in the DOM) - if ( vAPI.specificHideStyle.parentNode !== null ) { - return; - } - // Put it back - var parent = document.head || document.documentElement; - if ( parent ) { - parent.appendChild(vAPI.specificHideStyle); - } -})(); - -/******************************************************************************/ -/******************************************************************************/ - // https://github.com/chrisaljoudi/uBlock/issues/7 var uBlockCollapser = (function() { @@ -310,11 +286,34 @@ var uBlockCollapser = (function() { return; } - //console.debug('Starts cosmetic filtering'); + //console.debug('Start cosmetic filtering'); //var timer = window.performance || Date; //var tStart = timer.now(); + // 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 checkStyleTags = function() { + var doc = document, + html = doc.documentElement, + head = doc.head, + newParent = html || head; + if ( newParent === null ) { + return; + } + var styles = vAPI.styles || [], + style, oldParent; + for ( var i = 0; i < styles.length; i++ ) { + style = styles[i]; + oldParent = style.parentNode; + if ( oldParent !== head && oldParent !== html ) { + newParent.appendChild(style); + } + } + }; + checkStyleTags(); + var queriedSelectors = {}; var injectedSelectors = {}; var lowGenericSelectors = []; @@ -428,7 +427,7 @@ var uBlockCollapser = (function() { 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;}')); - var parent = document.body || document.documentElement; + var parent = document.head || document.documentElement; if ( parent ) { parent.appendChild(style); vAPI.styles.push(style); @@ -639,15 +638,12 @@ var uBlockCollapser = (function() { if ( injectedSelectors.hasOwnProperty('{{highHighGenerics}}') ) { return; } - //var tStart = timer.now(); + // When there are too many misses for these highly generic CSS rules, + // we will just give up on looking whether they need to be injected. if ( document.querySelector(highGenerics.hideHigh) === null ) { - //console.debug('%f: high-high generic test time', timer.now() - tStart); processHighHighGenericsMisses -= 1; - // Too many misses for these nagging highly generic CSS rules, - // so we will just skip them from now on. if ( processHighHighGenericsMisses === 0 ) { injectedSelectors['{{highHighGenerics}}'] = true; - //console.debug('high-high generic: apparently not needed...'); } return; } @@ -783,9 +779,15 @@ var uBlockCollapser = (function() { // Added node lists will be cumulated here before being processed var addedNodeLists = []; var addedNodeListsTimer = null; + var removedNodeListsTimer = null; var collapser = uBlockCollapser; - var treeMutationObservedHandler = function() { + // The `cosmeticFiltersActivated` message is required: a new element could + // be matching an already injected but otherwise inactive cosmetic filter. + // This means the already injected cosmetic filter become active (has an + // effect on the document), and thus must be logged if needed. + var addedNodesHandler = function() { + addedNodeListsTimer = null; var nodeList, iNode, node; while ( (nodeList = addedNodeLists.pop()) ) { iNode = nodeList.length; @@ -801,51 +803,60 @@ var uBlockCollapser = (function() { collapser.iframesFromNode(node); } } - addedNodeListsTimer = null; if ( contextNodes.length !== 0 ) { idsFromNodeList(selectNodes('[id]')); classesFromNodeList(selectNodes('[class]')); retrieveGenericSelectors(); - - // This is required: a new element could be matching an already - // injected but otherwise inactive cosmetic filter. This means - // the already injected cosmetic filter become active (has an - // effect on the document), and thus must be logged if needed. messager.send({ what: 'cosmeticFiltersActivated' }); } }; + // https://github.com/gorhill/uBlock/issues/873 + // This will ensure our style elements will stay in the DOM. + var removedNodesHandler = function() { + removedNodeListsTimer = null; + checkStyleTags(); + }; + // https://github.com/chrisaljoudi/uBlock/issues/205 // Do not handle added node directly from within mutation observer. - var treeMutationObservedHandlerAsync = function(mutations) { + // 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) { + var removedNodeLists = false; var iMutation = mutations.length; - var nodeList; + var nodeList, mutation; while ( iMutation-- ) { - nodeList = mutations[iMutation].addedNodes; + mutation = mutations[iMutation]; + nodeList = mutation.addedNodes; if ( nodeList.length !== 0 ) { addedNodeLists.push(nodeList); } + if ( mutation.removedNodes.length !== 0 ) { + removedNodeLists = true; + } } - if ( addedNodeListsTimer === null ) { - // 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. - addedNodeListsTimer = vAPI.setTimeout(treeMutationObservedHandler, 100); + if ( addedNodeLists.length !== 0 && addedNodeListsTimer === null ) { + addedNodeListsTimer = vAPI.setTimeout(addedNodesHandler, 100); + } + if ( removedNodeLists && removedNodeListsTimer === null ) { + removedNodeListsTimer = vAPI.setTimeout(removedNodesHandler, 100); } }; //console.debug('Starts cosmetic filtering\'s mutations observer'); // https://github.com/gorhill/httpswitchboard/issues/176 - var treeObserver = new MutationObserver(treeMutationObservedHandlerAsync); - treeObserver.observe(document.body, { + var domLayoutObserver = new MutationObserver(domLayoutChanged); + domLayoutObserver.observe(document.body, { childList: true, subtree: true }); // https://github.com/gorhill/uMatrix/issues/144 vAPI.shutdown.add(function() { - treeObserver.disconnect(); + domLayoutObserver.disconnect(); if ( addedNodeListsTimer !== null ) { clearTimeout(addedNodeListsTimer); } diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js index a6250851b..2f8f3beb6 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -98,7 +98,7 @@ var cosmeticFilters = function(details) { } if ( hide.length !== 0 ) { var text = hide.join(',\n'); - var style = vAPI.specificHideStyle = document.createElement('style'); + var style = document.createElement('style'); // The linefeed before the style block is very important: do not remove! style.appendChild(document.createTextNode(text + '\n{display:none !important;}')); //console.debug('µBlock> "%s" cosmetic filters: injecting %d CSS rules:', details.domain, details.hide.length, hideStyleText);