mirror of https://github.com/gorhill/uBlock.git
461 lines
15 KiB
JavaScript
461 lines
15 KiB
JavaScript
/*******************************************************************************
|
|
|
|
µBlock - a Chromium browser extension to block requests.
|
|
Copyright (C) 2014 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
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
Home: https://github.com/gorhill/uBlock
|
|
*/
|
|
|
|
/* jshint bitwise: false */
|
|
/* global µBlock */
|
|
|
|
/*******************************************************************************
|
|
|
|
A PageRequestStore object is used to store net requests in two ways:
|
|
|
|
To record distinct net requests
|
|
To create a log of net requests
|
|
|
|
**/
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
µBlock.PageStore = (function() {
|
|
|
|
/******************************************************************************/
|
|
|
|
var µb = µBlock;
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
// To mitigate memory churning
|
|
var netFilteringResultCacheEntryJunkyard = [];
|
|
var netFilteringResultCacheEntryJunkyardMax = 200;
|
|
|
|
/******************************************************************************/
|
|
|
|
var NetFilteringResultCacheEntry = function(result, type, flags) {
|
|
this.init(result, type, flags);
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCacheEntry.prototype.init = function(result, type, flags) {
|
|
this.result = result;
|
|
this.type = type;
|
|
this.flags = flags;
|
|
this.time = Date.now();
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCacheEntry.prototype.dispose = function() {
|
|
this.result = '';
|
|
this.type = '';
|
|
if ( netFilteringResultCacheEntryJunkyard.length < netFilteringResultCacheEntryJunkyardMax ) {
|
|
netFilteringResultCacheEntryJunkyard.push(this);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCacheEntry.factory = function(result, type, flags) {
|
|
var entry = netFilteringResultCacheEntryJunkyard.pop();
|
|
if ( entry === undefined ) {
|
|
entry = new NetFilteringResultCacheEntry(result, type, flags);
|
|
} else {
|
|
entry.init(result, type, flags);
|
|
}
|
|
return entry;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
// To mitigate memory churning
|
|
var uidGenerator = 1;
|
|
var netFilteringCacheJunkyard = [];
|
|
var netFilteringCacheJunkyardMax = 10;
|
|
|
|
/******************************************************************************/
|
|
|
|
var NetFilteringResultCache = function() {
|
|
this.init();
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCache.factory = function() {
|
|
var entry = netFilteringCacheJunkyard.pop();
|
|
if ( entry === undefined ) {
|
|
entry = new NetFilteringResultCache();
|
|
} else {
|
|
entry.init();
|
|
}
|
|
return entry;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCache.prototype.init = function() {
|
|
this.uname = 'NetFilteringResultCache:' + uidGenerator++;
|
|
this.urls = {};
|
|
this.count = 0;
|
|
this.shelfLife = 60 * 1000;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCache.prototype.dispose = function() {
|
|
for ( var key in this.urls ) {
|
|
if ( this.urls.hasOwnProperty(key) === false ) {
|
|
continue;
|
|
}
|
|
this.urls[key].dispose();
|
|
}
|
|
µBlock.asyncJobs.remove(this.uname);
|
|
this.uname = '';
|
|
this.urls = {};
|
|
this.count = 0;
|
|
if ( netFilteringCacheJunkyard.length < netFilteringCacheJunkyardMax ) {
|
|
netFilteringCacheJunkyard.push(this);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCache.prototype.add = function(url, result, type, flags) {
|
|
var entry = this.urls[url];
|
|
if ( entry !== undefined ) {
|
|
entry.result = result;
|
|
entry.type = type;
|
|
entry.flags = flags;
|
|
entry.time = Date.now();
|
|
return;
|
|
}
|
|
this.urls[url] = NetFilteringResultCacheEntry.factory(result, type, flags);
|
|
if ( this.count === 0 ) {
|
|
this.pruneAsync();
|
|
}
|
|
this.count += 1;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCache.prototype.fetchAll = function() {
|
|
return this.urls;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCache.prototype.compareEntries = function(a, b) {
|
|
return this.urls[b].time - this.urls[a].time;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCache.prototype.prune = function() {
|
|
var keys = Object.keys(this.urls).sort(this.compareEntries.bind(this));
|
|
var obsolete = Date.now() - this.shelfLife;
|
|
var key, entry;
|
|
var i = keys.length;
|
|
while ( i-- ) {
|
|
key = keys[i];
|
|
entry = this.urls[key];
|
|
if ( entry.time > obsolete ) {
|
|
break;
|
|
}
|
|
entry.dispose();
|
|
delete this.urls[key];
|
|
}
|
|
this.count -= keys.length - i - 1;
|
|
if ( this.count > 0 ) {
|
|
this.pruneAsync();
|
|
}
|
|
};
|
|
|
|
// https://www.youtube.com/watch?v=hcVpbsDyOhM
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCache.prototype.pruneAsync = function() {
|
|
µBlock.asyncJobs.add(
|
|
this.uname,
|
|
null,
|
|
this.prune.bind(this),
|
|
this.shelfLife + 120000,
|
|
false
|
|
);
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
NetFilteringResultCache.prototype.lookup = function(url) {
|
|
return this.urls[url];
|
|
};
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
// To mitigate memory churning
|
|
var frameStoreJunkyard = [];
|
|
var frameStoreJunkyardMax = 50;
|
|
|
|
/******************************************************************************/
|
|
|
|
var FrameStore = function(frameURL) {
|
|
this.init(frameURL);
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
FrameStore.factory = function(frameURL) {
|
|
var entry = frameStoreJunkyard.pop();
|
|
if ( entry === undefined ) {
|
|
entry = new FrameStore(frameURL);
|
|
} else {
|
|
entry.init(frameURL);
|
|
}
|
|
return entry;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
FrameStore.prototype.init = function(frameURL) {
|
|
var µburi = µb.URI;
|
|
this.pageHostname = µburi.hostnameFromURI(frameURL);
|
|
this.pageDomain = µburi.domainFromHostname(this.pageHostname);
|
|
return this;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
FrameStore.prototype.dispose = function() {
|
|
this.pageHostname = this.pageDomain = '';
|
|
if ( frameStoreJunkyard.length < frameStoreJunkyardMax ) {
|
|
frameStoreJunkyard.push(this);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
/******************************************************************************/
|
|
|
|
// To mitigate memory churning
|
|
var pageStoreJunkyard = [];
|
|
var pageStoreJunkyardMax = 10;
|
|
|
|
/******************************************************************************/
|
|
|
|
// Cache only what is worth it if logging is disabled
|
|
// http://jsperf.com/string-indexof-vs-object
|
|
var collapsibleRequestTypes = 'image sub_frame object';
|
|
|
|
/******************************************************************************/
|
|
|
|
var PageStore = function(tabId, pageURL) {
|
|
this.init(tabId, pageURL);
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.factory = function(tabId, pageURL) {
|
|
var entry = pageStoreJunkyard.pop();
|
|
if ( entry === undefined ) {
|
|
entry = new PageStore(tabId, pageURL);
|
|
} else {
|
|
entry.init(tabId, pageURL);
|
|
}
|
|
return entry;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.init = function(tabId, pageURL) {
|
|
this.tabId = tabId;
|
|
this.previousPageURL = '';
|
|
this.pageURL = pageURL;
|
|
this.pageHostname = µb.URI.hostnameFromURI(pageURL);
|
|
|
|
// https://github.com/gorhill/uBlock/issues/185
|
|
// Use hostname if no domain can be extracted
|
|
this.pageDomain = µb.URI.domainFromHostname(this.pageHostname) || this.pageHostname;
|
|
|
|
this.frames = {};
|
|
this.netFiltering = true;
|
|
this.netFilteringReadTime = 0;
|
|
this.perLoadBlockedRequestCount = 0;
|
|
this.perLoadAllowedRequestCount = 0;
|
|
|
|
this.netFilteringCache = NetFilteringResultCache.factory();
|
|
if ( µb.userSettings.logRequests ) {
|
|
this.netFilteringCache.shelfLife = 30 * 60 * 1000;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.reuse = function(pageURL, context) {
|
|
// If URL changes without a page reload (more and more common), then we
|
|
// need to keep all that we collected for reuse. In particular, not
|
|
// doing so was causing a problem in `videos.foxnews.com`: clicking a
|
|
// video thumbnail would not work, because the frame hierarchy structure
|
|
// was flushed from memory, while not really being flushed on the page.
|
|
if ( context === 'tabUpdated' ) {
|
|
this.previousPageURL = this.pageURL;
|
|
this.pageURL = pageURL;
|
|
this.pageHostname = µb.URI.hostnameFromURI(pageURL);
|
|
this.pageDomain = µb.URI.domainFromHostname(this.pageHostname) || this.pageHostname;
|
|
return this;
|
|
}
|
|
// A new page is completely reloaded from scratch, reset all.
|
|
this.disposeFrameStores();
|
|
this.netFilteringCache = this.netFilteringCache.dispose();
|
|
var previousPageURL = this.pageURL;
|
|
this.init(this.tabId, pageURL);
|
|
this.previousPageURL = previousPageURL;
|
|
return this;
|
|
};
|
|
|
|
// https://www.youtube.com/watch?v=dltNSbOupgE
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.dispose = function() {
|
|
// rhill 2013-11-07: Even though at init time these are reset, I still
|
|
// need to release the memory taken by these, which can amount to
|
|
// sizeable enough chunks (especially requests, through the request URL
|
|
// used as a key).
|
|
this.pageURL = '';
|
|
this.previousPageURL = '';
|
|
this.pageHostname = '';
|
|
this.pageDomain = '';
|
|
this.disposeFrameStores();
|
|
this.netFilteringCache = this.netFilteringCache.dispose();
|
|
if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) {
|
|
pageStoreJunkyard.push(this);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.disposeFrameStores = function() {
|
|
var frames = this.frames;
|
|
if ( typeof frames === 'object' ) {
|
|
for ( var k in frames ) {
|
|
if ( frames.hasOwnProperty(k) === false ) {
|
|
continue;
|
|
}
|
|
frames[k].dispose();
|
|
}
|
|
}
|
|
this.frames = {};
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.addFrame = function(frameId, frameURL) {
|
|
var frameStore = this.frames[frameId];
|
|
if ( frameStore === undefined ) {
|
|
this.frames[frameId] = frameStore = FrameStore.factory(frameURL);
|
|
//console.debug('µBlock> PageStore.addFrame(%d, "%s")', frameId, frameURL);
|
|
}
|
|
return frameStore;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.getFrame = function(frameId) {
|
|
return this.frames[frameId];
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.getNetFilteringSwitch = function() {
|
|
if ( this.netFilteringReadTime < µb.netWhitelistModifyTime ) {
|
|
this.netFiltering = µb.getNetFilteringSwitch(this.pageURL, this.pageDomain);
|
|
this.netFilteringReadTime = Date.now();
|
|
}
|
|
return this.netFiltering;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.filterRequest = function(context, requestType, requestURL) {
|
|
var entry = this.netFilteringCache.lookup(requestURL);
|
|
if ( entry !== undefined ) {
|
|
//console.debug(' cache HIT: PageStore.filterRequest("%s")', requestURL);
|
|
return entry.result;
|
|
}
|
|
//console.debug('cache MISS: PageStore.filterRequest("%s")', requestURL);
|
|
var result = µb.netFilteringEngine.matchString(context, requestURL, requestType);
|
|
if ( collapsibleRequestTypes.indexOf(requestType) !== -1 || µb.userSettings.logRequests ) {
|
|
this.netFilteringCache.add(requestURL, result, requestType, 0);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.setRequestFlags = function(requestURL, targetBits, valueBits) {
|
|
var entry = this.netFilteringCache.lookup(requestURL);
|
|
if ( entry !== undefined ) {
|
|
entry.flags = (entry.flags & ~targetBits) | (valueBits & targetBits);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// false: not blocked
|
|
// true: blocked
|
|
|
|
PageStore.prototype.boolFromResult = function(result) {
|
|
return typeof result === 'string' && result !== '' && result.slice(0, 2) !== '@@';
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.updateBadge = function() {
|
|
var netFiltering = this.getNetFilteringSwitch();
|
|
var iconPaths = netFiltering ?
|
|
{ '19': 'img/browsericons/icon19.png', '38': 'img/browsericons/icon38.png' } :
|
|
{ '19': 'img/browsericons/icon19-off.png', '38': 'img/browsericons/icon38-off.png' };
|
|
|
|
var iconStr = '';
|
|
if ( µb.userSettings.showIconBadge && netFiltering && this.perLoadBlockedRequestCount ) {
|
|
iconStr = µb.utils.formatCount(this.perLoadBlockedRequestCount);
|
|
}
|
|
µb.XAL.setIcon(this.tabId, iconPaths, iconStr);
|
|
};
|
|
|
|
// https://www.youtube.com/watch?v=drW8p_dTLD4
|
|
|
|
/******************************************************************************/
|
|
|
|
return {
|
|
factory: PageStore.factory
|
|
};
|
|
|
|
})();
|
|
|
|
/******************************************************************************/
|