code review domCollapser: avoid duplicates -- help for https://bugzilla.mozilla.org/show_bug.cgi?id=1232354

This commit is contained in:
gorhill 2016-08-08 09:53:35 -04:00
parent 9700c32bc9
commit 1774a7721f
3 changed files with 69 additions and 56 deletions

View File

@ -689,10 +689,9 @@ return domFilterer;
var domCollapser = (function() {
var timer = null;
var requestId = 1;
var newRequests = [];
var pendingRequests = Object.create(null);
var pendingRequestCount = 0;
var roundtripRequests = [];
var src1stProps = {
'embed': 'src',
'img': 'src',
@ -701,22 +700,14 @@ var domCollapser = (function() {
var src2ndProps = {
'img': 'srcset'
};
var netSelectorCacheCount = 0;
var messaging = vAPI.messaging;
var PendingRequest = function(target, tagName, attr) {
this.id = requestId++;
this.target = target;
this.tagName = tagName;
this.attr = attr;
pendingRequests[this.id] = this;
pendingRequestCount += 1;
};
// Because a while ago I have observed constructors are faster than
// literal object instanciations.
var BouncingRequest = function(id, tagName, url) {
this.id = id;
this.tagName = tagName;
var RoundtripRequest = function(tag, attr, url) {
this.tag = tag;
this.attr = attr;
this.url = url;
this.collapse = false;
};
@ -736,34 +727,43 @@ var domCollapser = (function() {
if ( requests === null || Array.isArray(requests) === false ) {
return;
}
var selectors = [];
var i = requests.length;
var request, entry, target, value;
while ( i-- ) {
var selectors = [],
netSelectorCacheCountMax = response.netSelectorCacheCountMax,
aa = [ null ],
request, key, entry, target, value;
// Important: process in chronological order -- this ensures the
// cached selectors are the most useful ones.
for ( var i = 0, ni = requests.length; i < ni; i++ ) {
request = requests[i];
entry = pendingRequests[request.id];
key = request.tag + ' ' + request.attr + ' ' + request.url;
entry = pendingRequests[key];
if ( entry === undefined ) {
continue;
}
delete pendingRequests[request.id];
pendingRequestCount -= 1;
delete pendingRequests[key];
// https://github.com/chrisaljoudi/uBlock/issues/869
if ( !request.collapse ) {
continue;
}
target = entry.target;
// https://github.com/chrisaljoudi/uBlock/issues/399
// Never remove elements from the DOM, just hide them
target.style.setProperty('display', 'none', 'important');
target.hidden = true;
// https://github.com/chrisaljoudi/uBlock/issues/1048
// Use attribute to construct CSS rule
if ( (value = target.getAttribute(entry.attr)) ) {
selectors.push(entry.tagName + '[' + entry.attr + '="' + value + '"]');
if ( Array.isArray(entry) === false ) {
aa[0] = entry;
entry = aa;
}
for ( var j = 0, nj = entry.length; j < nj; j++ ) {
target = entry[j];
// https://github.com/chrisaljoudi/uBlock/issues/399
// Never remove elements from the DOM, just hide them
target.style.setProperty('display', 'none', 'important');
target.hidden = true;
// https://github.com/chrisaljoudi/uBlock/issues/1048
// Use attribute to construct CSS rule
if (
netSelectorCacheCount <= netSelectorCacheCountMax &&
(value = target.getAttribute(request.attr))
) {
selectors.push(request.tag + '[' + request.attr + '="' + value + '"]');
netSelectorCacheCount += 1;
}
}
}
if ( selectors.length !== 0 ) {
@ -777,11 +777,6 @@ var domCollapser = (function() {
}
);
}
// Renew map: I believe that even if all properties are deleted, an
// object will still use more memory than a brand new one.
if ( pendingRequestCount === 0 ) {
pendingRequests = Object.create(null);
}
};
var send = function() {
@ -792,14 +787,14 @@ var domCollapser = (function() {
what: 'filterRequests',
pageURL: window.location.href,
pageHostname: window.location.hostname,
requests: newRequests
requests: roundtripRequests
}, onProcessed
);
newRequests = [];
};
var process = function(delay) {
if ( newRequests.length === 0 ) {
if ( roundtripRequests.length === 0 ) {
return;
}
if ( delay === 0 ) {
@ -814,8 +809,8 @@ var domCollapser = (function() {
// for iframes.
var add = function(target) {
var tagName = target.localName;
var prop = src1stProps[tagName];
var tag = target.localName;
var prop = src1stProps[tag];
if ( prop === undefined ) {
return;
}
@ -823,7 +818,7 @@ var domCollapser = (function() {
// Do not remove fragment from src URL
var src = target[prop];
if ( typeof src !== 'string' || src.length === 0 ) {
prop = src2ndProps[tagName];
prop = src2ndProps[tag];
if ( prop === undefined ) {
return;
}
@ -837,8 +832,16 @@ var domCollapser = (function() {
if ( src.lastIndexOf('data:', 0) === 0 ) {
src = src.slice(0, 255);
}
var req = new PendingRequest(target, tagName, prop);
newRequests.push(new BouncingRequest(req.id, tagName, src));
var key = tag + ' ' + prop + ' ' + src,
entry = pendingRequests[key];
if ( entry === undefined ) {
pendingRequests[key] = target;
roundtripRequests.push(new RoundtripRequest(tag, prop, src));
} else if ( Array.isArray(entry) ) {
entry.push(target);
} else {
pendingRequests[key] = [ entry, target ];
}
};
var addMany = function(targets) {
@ -892,8 +895,16 @@ var domCollapser = (function() {
if ( src.lastIndexOf('http', 0) !== 0 ) {
return;
}
var req = new PendingRequest(iframe, 'iframe', 'src');
newRequests.push(new BouncingRequest(req.id, 'iframe', src));
var key = 'iframe' + ' ' + 'src' + ' ' + src,
entry = pendingRequests[key];
if ( entry === undefined ) {
pendingRequests[key] = iframe;
roundtripRequests.push(new RoundtripRequest('iframe', 'src', src));
} else if ( Array.isArray(entry) ) {
entry.push(iframe);
} else {
pendingRequests[key] = [ entry, iframe ];
}
};
var addIFrames = function(iframes) {
@ -1453,8 +1464,8 @@ skip-survey=false: survey-phase-1 => survey-phase-2 => survey-phase-3 => commit
// http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
(function() {
var collapser = domCollapser;
var elems = document.images || document.getElementsByTagName('img'),
var collapser = domCollapser,
elems = document.images || document.getElementsByTagName('img'),
i = elems.length, elem;
while ( i-- ) {
elem = elems[i];

View File

@ -458,8 +458,8 @@ SelectorCacheEntry.factory = function() {
/******************************************************************************/
SelectorCacheEntry.prototype.netLowWaterMark = 20;
SelectorCacheEntry.prototype.netHighWaterMark = 30;
var netSelectorCacheLowWaterMark = 20;
var netSelectorCacheHighWaterMark = 30;
/******************************************************************************/
@ -507,13 +507,13 @@ SelectorCacheEntry.prototype.addNet = function(selectors) {
// Net request-derived selectors: I limit the number of cached selectors,
// as I expect cases where the blocked net-requests are never the
// exact same URL.
if ( this.netCount < this.netHighWaterMark ) {
if ( this.netCount < netSelectorCacheHighWaterMark ) {
return;
}
var dict = this.net;
var keys = Object.keys(dict).sort(function(a, b) {
return dict[b] - dict[a];
}).slice(this.netLowWaterMark);
}).slice(netSelectorCacheLowWaterMark);
var i = keys.length;
while ( i-- ) {
delete dict[keys[i]];
@ -664,6 +664,7 @@ var FilterContainer = function() {
this.selectorCachePruneDelay = 10 * 60 * 1000; // 15 minutes
this.selectorCacheAgeMax = 120 * 60 * 1000; // 120 minutes
this.selectorCacheCountMin = 25;
this.netSelectorCacheCountMax = netSelectorCacheHighWaterMark;
this.selectorCacheTimer = null;
this.reHasUnicode = /[^\x00-\x7F]/;
this.punycode = punycode;

View File

@ -484,7 +484,7 @@ var filterRequests = function(pageStore, details) {
context.requestURL = punycodeURL(request.url);
context.requestHostname = hostnameFromURI(context.requestURL);
}
context.requestType = tagNameToRequestTypeMap[request.tagName];
context.requestType = tagNameToRequestTypeMap[request.tag];
r = pageStore.filterRequest(context);
if ( typeof r !== 'string' || r.charAt(1) !== 'b' ) {
continue;
@ -540,7 +540,8 @@ var onMessage = function(request, sender, callback) {
case 'filterRequests':
response = {
shutdown: !pageStore || !pageStore.getNetFilteringSwitch(),
result: null
result: null,
netSelectorCacheCountMax: µb.cosmeticFilteringEngine.netSelectorCacheCountMax
};
if ( !response.shutdown ) {
response.result = filterRequests(pageStore, request);