From e99d993a4c669a4352b42536b92dbe8580f5c668 Mon Sep 17 00:00:00 2001 From: gorhill Date: Tue, 28 Jun 2016 22:01:15 -0400 Subject: [PATCH] new content script code: perf work re. high-high generics Now splitting high-high generics in two subgroups: one group for simple selectors, another group for complex selectors. Turns out the great majority of high-high generics are simple selectors, and simple selectors can be applied incrementally with DOM changes, as opposed to complex selectors. This brings in a significant perf. improvement in the processing of high-high generics (previously, all high-high generic selectors were processed as one big complex selector). --- src/js/background.js | 9 ++- src/js/contentscript.js | 114 ++++++++++++++++++--------------- src/js/cosmetic-filtering.js | 66 +++++++++++++------ src/js/reverselookup-worker.js | 6 +- 4 files changed, 117 insertions(+), 78 deletions(-) diff --git a/src/js/background.js b/src/js/background.js index d8ae29787..d0d92f754 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -1,7 +1,7 @@ /******************************************************************************* - uBlock - a browser extension to block requests. - Copyright (C) 2014-2015 Raymond Hill + uBlock Origin - a browser extension to block requests. + Copyright (C) 2014-2016 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 @@ -19,7 +19,6 @@ Home: https://github.com/gorhill/uBlock */ -/* global vAPI */ /* exported µBlock */ 'use strict'; @@ -92,8 +91,8 @@ return { // read-only systemSettings: { - compiledMagic: 'nytangedtvcz', - selfieMagic: 'emzolxctioww' + compiledMagic: 'splsmclwnvoj', + selfieMagic: 'splsmclwnvoj' }, restoreBackupSettings: { diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 215231f53..10c35debc 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -38,6 +38,18 @@ if ( typeof vAPI !== 'object' || vAPI.contentscriptInjected ) { vAPI.contentscriptInjected = true; +vAPI.matchesProp = (function() { + var docElem = document.documentElement; + if ( typeof docElem.matches !== 'function' ) { + if ( typeof docElem.mozMatchesSelector === 'function' ) { + return 'mozMatchesSelector'; + } else if ( typeof docElem.webkitMatchesSelector === 'function' ) { + return 'webkitMatchesSelector'; + } + } + return 'matches'; +})(); + /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ @@ -53,7 +65,7 @@ vAPI.domFilterer = { enabled: true, hiddenId: String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 982451653 + 982451653).toString(36), hiddenNodeCount: 0, - matchesProp: 'matches', + matchesProp: vAPI.matchesProp, newDeclarativeSelectors: [], shadowId: String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 982451653 + 982451653).toString(36), styleTags: [], @@ -410,14 +422,6 @@ vAPI.domFilterer = { var df = vAPI.domFilterer; df.cssNotHiddenId = ':not([' + df.hiddenId + '])'; df.xpathNotHiddenId = '[not(@' + df.hiddenId + ')]'; - var docElem = document.documentElement; - if ( typeof docElem.matches !== 'function' ) { - if ( typeof docElem.mozMatchesSelector === 'function' ) { - df.matchesProp = 'mozMatchesSelector'; - } else if ( typeof docElem.webkitMatchesSelector === 'function' ) { - df.matchesProp = 'webkitMatchesSelector'; - } - } // Complex selectors, due to their nature may need to be "de-committed". A // Set() is used to implement this functionality. For browser with no @@ -835,12 +839,11 @@ if ( !vAPI.contentscriptInjected ) { domFilterer.checkStyleTags(false); domFilterer.commit(); - var contextNodes = [ document.documentElement ]; - var messaging = vAPI.messaging; - var highGenerics = null; - var highHighGenericsInjected = false; - var lowGenericSelectors = []; - var queriedSelectors = Object.create(null); + var contextNodes = [ document.documentElement ], + messaging = vAPI.messaging, + highGenerics = null, + lowGenericSelectors = [], + queriedSelectors = Object.create(null); var responseHandler = function(response) { // https://github.com/gorhill/uMatrix/issues/144 @@ -859,6 +862,7 @@ if ( !vAPI.contentscriptInjected ) { highGenerics = result.highGenerics; } } + if ( highGenerics ) { if ( highGenerics.hideLowCount ) { processHighLowGenerics(highGenerics.hideLow); @@ -866,10 +870,11 @@ if ( !vAPI.contentscriptInjected ) { if ( highGenerics.hideMediumCount ) { processHighMediumGenerics(highGenerics.hideMedium); } - if ( highGenerics.hideHighCount ) { - processHighHighGenericsAsync(); + if ( highGenerics.hideHighSimpleCount || highGenerics.hideHighComplexCount ) { + processHighHighGenerics(); } } + domFilterer.commit(contextNodes); contextNodes = []; }; @@ -886,10 +891,10 @@ if ( !vAPI.contentscriptInjected ) { }, responseHandler ); + lowGenericSelectors = []; } else { responseHandler(null); } - lowGenericSelectors = []; }; // Extract and return the staged nodes which (may) match the selectors. @@ -1009,42 +1014,50 @@ if ( !vAPI.contentscriptInjected ) { } }; - // High-high generics are very costly to process, so we will coalesce - // requests to process high-high generics into as few requests as possible. - // The gain is significant on bloated pages. - - var processHighHighGenericsMisses = 8; - var processHighHighGenericsTimer = null; + var highHighSimpleGenericsCost = 0, + highHighSimpleGenericsInjected = false, + highHighComplexGenericsCost = 0, + highHighComplexGenericsInjected = false; var processHighHighGenerics = function() { - processHighHighGenericsTimer = null; - if ( highGenerics.hideHigh === '' ) { - return; - } - if ( highHighGenericsInjected ) { - return; - } - // 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 ) { - processHighHighGenericsMisses -= 1; - if ( processHighHighGenericsMisses === 0 ) { - highHighGenericsInjected = true; + 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; + } } - return; + highHighSimpleGenericsCost += window.performance.now() - tstart; } - highHighGenericsInjected = true; - // We need to filter out possible exception cosmetic filters from - // high-high generics selectors. - domFilterer.addSelectors(highGenerics.hideHigh.split(',\n')); - domFilterer.commit(); - }; - - var processHighHighGenericsAsync = function() { - if ( processHighHighGenericsTimer !== null ) { - clearTimeout(processHighHighGenericsTimer); + // 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; } - processHighHighGenericsTimer = vAPI.setTimeout(processHighHighGenerics, 300); }; // Extract all classes/ids: these will be passed to the cosmetic filtering @@ -1225,9 +1238,6 @@ if ( !vAPI.contentscriptInjected ) { if ( removedNodeListsTimer !== null ) { clearTimeout(removedNodeListsTimer); } - if ( processHighHighGenericsTimer !== null ) { - clearTimeout(processHighHighGenericsTimer); - } }); })(); diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 94e2653b7..0cd905d62 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -672,10 +672,15 @@ FilterContainer.prototype.reset = function() { this.highMediumGenericHide = {}; this.highMediumGenericHideCount = 0; - // everything else - this.highHighGenericHideArray = []; - this.highHighGenericHide = ''; - this.highHighGenericHideCount = 0; + // high-high-simple selectors + this.highHighSimpleGenericHideArray = []; + this.highHighSimpleGenericHide = ''; + this.highHighSimpleGenericHideCount = 0; + + // high-high-complex selectors + this.highHighComplexGenericHideArray = []; + this.highHighComplexGenericHide = ''; + this.highHighComplexGenericHideCount = 0; // generic exception filters this.genericDonthide = []; @@ -888,8 +893,13 @@ FilterContainer.prototype.compileGenericSelector = function(parsed, out) { return; } - // All else - out.push('c\vhhg0\v' + selector); + // All else: high-high generics. + // Distinguish simple vs complex selectors. + if ( selector.indexOf(' ') === -1 ) { + out.push('c\vhhsg0\v' + selector); + } else { + out.push('c\vhhcg0\v' + selector); + } }; FilterContainer.prototype.reClassOrIdSelector = /^[#.][\w-]+$/; @@ -1048,9 +1058,15 @@ FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) { continue; } - if ( fields[0] === 'hhg0' ) { - this.highHighGenericHideArray.push(fields[1]); - this.highHighGenericHideCount += 1; + if ( fields[0] === 'hhsg0' ) { + this.highHighSimpleGenericHideArray.push(fields[1]); + this.highHighSimpleGenericHideCount += 1; + continue; + } + + if ( fields[0] === 'hhcg0' ) { + this.highHighComplexGenericHideArray.push(fields[1]); + this.highHighComplexGenericHideCount += 1; continue; } @@ -1197,11 +1213,17 @@ FilterContainer.prototype.retrieveScriptTags = function(domain, hostname) { FilterContainer.prototype.freeze = function() { this.duplicateBuster = {}; - if ( this.highHighGenericHide !== '' ) { - this.highHighGenericHideArray.unshift(this.highHighGenericHide); + if ( this.highHighSimpleGenericHide !== '' ) { + this.highHighSimpleGenericHideArray.unshift(this.highHighSimpleGenericHide); } - this.highHighGenericHide = this.highHighGenericHideArray.join(',\n'); - this.highHighGenericHideArray = []; + this.highHighSimpleGenericHide = this.highHighSimpleGenericHideArray.join(',\n'); + this.highHighSimpleGenericHideArray = []; + + if ( this.highHighComplexGenericHide !== '' ) { + this.highHighComplexGenericHideArray.unshift(this.highHighComplexGenericHide); + } + this.highHighComplexGenericHide = this.highHighComplexGenericHideArray.join(',\n'); + this.highHighComplexGenericHideArray = []; this.parser.reset(); this.frozen = true; @@ -1245,8 +1267,10 @@ FilterContainer.prototype.toSelfie = function() { highLowGenericHideCount: this.highLowGenericHideCount, highMediumGenericHide: this.highMediumGenericHide, highMediumGenericHideCount: this.highMediumGenericHideCount, - highHighGenericHide: this.highHighGenericHide, - highHighGenericHideCount: this.highHighGenericHideCount, + highHighSimpleGenericHide: this.highHighSimpleGenericHide, + highHighSimpleGenericHideCount: this.highHighSimpleGenericHideCount, + highHighComplexGenericHide: this.highHighComplexGenericHide, + highHighComplexGenericHideCount: this.highHighComplexGenericHideCount, genericDonthide: this.genericDonthide, scriptTagFilters: this.scriptTagFilters, scriptTagFilterCount: this.scriptTagFilterCount, @@ -1309,8 +1333,10 @@ FilterContainer.prototype.fromSelfie = function(selfie) { this.highLowGenericHideCount = selfie.highLowGenericHideCount; this.highMediumGenericHide = selfie.highMediumGenericHide; this.highMediumGenericHideCount = selfie.highMediumGenericHideCount; - this.highHighGenericHide = selfie.highHighGenericHide; - this.highHighGenericHideCount = selfie.highHighGenericHideCount; + this.highHighSimpleGenericHide = selfie.highHighSimpleGenericHide; + this.highHighSimpleGenericHideCount = selfie.highHighSimpleGenericHideCount; + this.highHighComplexGenericHide = selfie.highHighComplexGenericHide; + this.highHighComplexGenericHideCount = selfie.highHighComplexGenericHideCount; this.genericDonthide = selfie.genericDonthide; this.scriptTagFilters = selfie.scriptTagFilters; this.scriptTagFilterCount = selfie.scriptTagFilterCount; @@ -1441,8 +1467,10 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) { hideLowCount: this.highLowGenericHideCount, hideMedium: this.highMediumGenericHide, hideMediumCount: this.highMediumGenericHideCount, - hideHigh: this.highHighGenericHide, - hideHighCount: this.highHighGenericHideCount + hideHighSimple: this.highHighSimpleGenericHide, + hideHighSimpleCount: this.highHighSimpleGenericHideCount, + hideHighComplex: this.highHighComplexGenericHide, + hideHighComplexCount: this.highHighComplexGenericHideCount }; } diff --git a/src/js/reverselookup-worker.js b/src/js/reverselookup-worker.js index dc1714d09..282e06444 100644 --- a/src/js/reverselookup-worker.js +++ b/src/js/reverselookup-worker.js @@ -125,8 +125,10 @@ var fromCosmeticFilter = function(details) { reStr.push('c', 'hlg0', reEscape(filter)); } else if ( reHighMedium.test(filter) ) { // [href^="..."] reStr.push('c', 'hmg0', '[^"]{8}', '[a-z]*' + reEscape(filter)); - } else { // all else - reStr.push('c', 'hhg0', reEscape(filter)); + } else if ( filter.indexOf(' ') === -1 ) { // high-high-simple selector + reStr.push('c', 'hhsg0', reEscape(filter)); + } else { // high-high-complex selector + reStr.push('c', 'hhcg0', reEscape(filter)); } candidates[details.rawFilter] = new RegExp(reStr.join('\\v') + '(?:\\n|$)');