1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-09-29 14:17:11 +02:00
uBlock/src/js/pagestore.js
Deathamns 0886f7e886 Add .jshintrc, and use the "use strict" directive
.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.
2014-11-09 17:39:17 +01:00

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
};
})();
/******************************************************************************/