mirror of
https://github.com/gorhill/uBlock.git
synced 2024-09-29 14:17:11 +02:00
0886f7e886
.jshintrc's otion-set is a personal choice, merely a suggestion. Beside that, it includes some common globals for specific browsers, so there's no need to set the globals in every .js file. In order to force strict coding, "use strict" directive was added into every .js file.
479 lines
16 KiB
JavaScript
479 lines
16 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, vAPI */
|
|
'use strict';
|
|
|
|
/*******************************************************************************
|
|
|
|
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(rootHostname, frameURL) {
|
|
this.init(rootHostname, frameURL);
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
FrameStore.factory = function(rootHostname, frameURL) {
|
|
var entry = frameStoreJunkyard.pop();
|
|
if ( entry === undefined ) {
|
|
entry = new FrameStore(rootHostname, frameURL);
|
|
} else {
|
|
entry.init(rootHostname, frameURL);
|
|
}
|
|
return entry;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
FrameStore.prototype.init = function(rootHostname, frameURL) {
|
|
var µburi = µb.URI;
|
|
this.pageHostname = µburi.hostnameFromURI(frameURL);
|
|
this.pageDomain = µburi.domainFromHostname(this.pageHostname) || this.pageHostname;
|
|
this.rootHostname = rootHostname;
|
|
this.rootDomain = µburi.domainFromHostname(rootHostname) || rootHostname;
|
|
return this;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
FrameStore.prototype.dispose = function() {
|
|
this.pageHostname = this.pageDomain =
|
|
this.rootHostname = this.rootDomain = '';
|
|
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.rootHostname = this.pageHostname;
|
|
this.rootDomain = this.pageDomain;
|
|
|
|
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;
|
|
this.rootHostname = this.pageHostname;
|
|
this.rootDomain = this.pageDomain;
|
|
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.rootHostname = this.rootDomain = '';
|
|
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(this.rootHostname, 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 result = '';
|
|
if ( this.getNetFilteringSwitch() ) {
|
|
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);
|
|
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);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
PageStore.prototype.recordResult = function(requestType, requestURL, result) {
|
|
if ( collapsibleRequestTypes.indexOf(requestType) !== -1 || µb.userSettings.logRequests ) {
|
|
this.netFilteringCache.add(requestURL, result, requestType, 0);
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
// 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);
|
|
}
|
|
vAPI.setIcon(this.tabId, iconPaths, iconStr);
|
|
};
|
|
|
|
// https://www.youtube.com/watch?v=drW8p_dTLD4
|
|
|
|
/******************************************************************************/
|
|
|
|
return {
|
|
factory: PageStore.factory
|
|
};
|
|
|
|
})();
|
|
|
|
/******************************************************************************/
|