diff --git a/src/js/contentscript.js b/src/js/contentscript.js index c4d29e574..e11d868cb 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -229,55 +229,75 @@ var platformHideNode = vAPI.hideNode, } var uid, - timerId, + timer, observer, - changedNodes = []; - var observerOptions = { + changedNodes = [], + observerOptions = { attributes: true, attributeFilter: [ 'style' ] }; - var overrideInlineStyle = function(node) { - var style = window.getComputedStyle(node), - display = style.getPropertyValue('display'), - attr = node.getAttribute('style') || ''; - if ( node[uid] === undefined ) { - node[uid] = node.hasAttribute('style') && attr; + // https://jsperf.com/clientheight-and-clientwidth-vs-getcomputedstyle + // Avoid getComputedStyle(), detecting whether a node is visible can be + // achieved with clientWidth/clientHeight. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a + // Do not interleave read-from/write-to the DOM. Write-to DOM + // operations would cause the first read-from to be expensive, and + // interleaving means that potentially all single read-from operation + // would be expensive rather than just the 1st one. + // Benchmarking toggling off/on cosmetic filtering confirms quite an + // improvement when: + // - batching as much as possible handling of all nodes; + // - avoiding to interleave read-from/write-to operations. + // However, toggling off/on cosmetic filtering repeatedly is not + // a real use case, but this shows this will help performance + // on sites which try to use inline styles to bypass blockers. + var batchProcess = function() { + timer.clear(); + var cNodes = changedNodes, i = cNodes.length, + vNodes = [], j = 0, + node; + while ( i-- ) { + node = cNodes[i]; + if ( node[uid] !== undefined && node.clientHeight && node.clientWidth ) { + vNodes[j++] = node; + } } - if ( display !== '' && display !== 'none' ) { - if ( attr !== '' ) { attr += '; '; } + cNodes.length = 0; + while ( j-- ) { + node = vNodes[j]; + var attr = node.getAttribute('style'); + if ( !attr ) { + attr = ''; + } else { + attr += '; '; + } node.setAttribute('style', attr + 'display: none !important;'); } }; - var timerHandler = function() { - timerId = undefined; - var nodes = changedNodes, - i = nodes.length, node; - while ( i-- ) { - node = nodes[i]; - if ( node[uid] !== undefined ) { - overrideInlineStyle(node); - } - } - nodes.length = 0; - }; - var observerHandler = function(mutations) { - var i = mutations.length; + var i = mutations.length, + cNodes = changedNodes, + j = cNodes.length; while ( i-- ) { - changedNodes.push(mutations[i].target); - } - if ( timerId === undefined ) { - timerId = vAPI.setTimeout(timerHandler, 1); + cNodes[j++] = mutations[i].target; } + timer.start(); }; platformHideNode = function(node) { if ( uid === undefined ) { uid = vAPI.randomToken(); + timer = new vAPI.SafeAnimationFrame(batchProcess); } - overrideInlineStyle(node); + if ( node[uid] === undefined ) { + node[uid] = node.hasAttribute('style') && (node.getAttribute('style') || ''); + } + // Performance: batch-process nodes to hide. + var cNodes = changedNodes; + cNodes[cNodes.length] = node; + timer.start(); if ( observer === undefined ) { observer = new MutationObserver(observerHandler); }