2014-06-24 00:42:43 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2015-03-07 19:20:18 +01:00
|
|
|
µBlock - a browser extension to block requests.
|
2014-06-24 00:42:43 +02:00
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2014-10-02 22:45:26 +02:00
|
|
|
/* jshint bitwise: false */
|
2014-12-17 16:09:08 +01:00
|
|
|
/* global vAPI, µBlock */
|
2014-06-24 00:42:43 +02:00
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
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() {
|
|
|
|
|
2014-12-17 16:09:08 +01:00
|
|
|
'use strict';
|
|
|
|
|
2014-06-24 00:42:43 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var µb = µBlock;
|
|
|
|
|
2014-09-14 22:20:40 +02:00
|
|
|
/******************************************************************************/
|
2014-06-24 00:42:43 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
var LogEntry = function(details, result) {
|
|
|
|
this.init(details, result);
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var logEntryFactory = function(details, result) {
|
|
|
|
var entry = logEntryJunkyard.pop();
|
|
|
|
if ( entry ) {
|
|
|
|
return entry.init(details, result);
|
|
|
|
}
|
|
|
|
return new LogEntry(details, result);
|
|
|
|
};
|
|
|
|
|
|
|
|
var logEntryJunkyard = [];
|
|
|
|
var logEntryJunkyardMax = 100;
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
LogEntry.prototype.init = function(details, result) {
|
|
|
|
this.tstamp = Date.now();
|
|
|
|
this.url = details.requestURL;
|
|
|
|
this.hostname = details.requestHostname;
|
|
|
|
this.type = details.requestType;
|
|
|
|
this.result = result;
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
LogEntry.prototype.dispose = function() {
|
2015-01-21 01:39:13 +01:00
|
|
|
this.url = this.hostname = this.type = this.result = '';
|
2015-01-06 14:01:15 +01:00
|
|
|
if ( logEntryJunkyard.length < logEntryJunkyardMax ) {
|
|
|
|
logEntryJunkyard.push(this);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var LogBuffer = function() {
|
|
|
|
this.lastReadTime = 0;
|
2015-01-09 20:34:50 +01:00
|
|
|
this.size = 50;
|
2015-01-06 14:01:15 +01:00
|
|
|
this.buffer = null;
|
|
|
|
this.readPtr = 0;
|
|
|
|
this.writePtr = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var logBufferFactory = function() {
|
|
|
|
return new LogBuffer();
|
|
|
|
};
|
|
|
|
|
|
|
|
var liveLogBuffers = [];
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
LogBuffer.prototype.dispose = function() {
|
|
|
|
if ( this.buffer === null ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
var entry;
|
|
|
|
var i = this.buffer.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
entry = this.buffer[i];
|
|
|
|
if ( entry instanceof LogEntry ) {
|
|
|
|
entry.dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.buffer = null;
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
LogBuffer.prototype.start = function() {
|
|
|
|
if ( this.buffer === null ) {
|
|
|
|
this.buffer = new Array(this.size);
|
|
|
|
this.readPtr = 0;
|
|
|
|
this.writePtr = 0;
|
|
|
|
liveLogBuffers.push(this);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
LogBuffer.prototype.stop = function() {
|
|
|
|
this.dispose();
|
|
|
|
this.buffer = null;
|
|
|
|
// The janitor will remove us from the live pool eventually.
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
LogBuffer.prototype.writeOne = function(details, result) {
|
|
|
|
if ( this.buffer === null ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Reusing log entry = less memory churning
|
|
|
|
var entry = this.buffer[this.writePtr];
|
|
|
|
if ( entry instanceof LogEntry === false ) {
|
|
|
|
this.buffer[this.writePtr] = logEntryFactory(details, result);
|
|
|
|
} else {
|
|
|
|
entry.init(details, result);
|
|
|
|
}
|
|
|
|
this.writePtr += 1;
|
|
|
|
if ( this.writePtr === this.size ) {
|
|
|
|
this.writePtr = 0;
|
|
|
|
}
|
|
|
|
// Grow the buffer between 1.5x-2x the current size
|
|
|
|
if ( this.writePtr === this.readPtr ) {
|
|
|
|
var toMove = this.buffer.slice(0, this.writePtr);
|
|
|
|
var minSize = Math.ceil(this.size * 1.5);
|
2015-01-07 17:36:23 +01:00
|
|
|
this.size += toMove.length;
|
2015-01-06 14:01:15 +01:00
|
|
|
if ( this.size < minSize ) {
|
|
|
|
this.buffer = this.buffer.concat(toMove, new Array(minSize - this.size));
|
|
|
|
this.writePtr = this.size;
|
|
|
|
} else {
|
|
|
|
this.buffer = this.buffer.concat(toMove);
|
|
|
|
this.writePtr = 0;
|
|
|
|
}
|
|
|
|
this.size = this.buffer.length;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
LogBuffer.prototype.readAll = function() {
|
|
|
|
var out;
|
|
|
|
if ( this.buffer === null ) {
|
|
|
|
this.start();
|
|
|
|
out = [];
|
|
|
|
} else if ( this.readPtr < this.writePtr ) {
|
|
|
|
out = this.buffer.slice(this.readPtr, this.writePtr);
|
|
|
|
} else if ( this.writePtr < this.readPtr ) {
|
|
|
|
out = this.buffer.slice(this.readPtr).concat(this.buffer.slice(0, this.writePtr));
|
|
|
|
} else {
|
|
|
|
out = [];
|
|
|
|
}
|
|
|
|
this.readPtr = this.writePtr;
|
|
|
|
this.lastReadTime = Date.now();
|
|
|
|
return out;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
var logBufferJanitor = function() {
|
|
|
|
var logBuffer;
|
|
|
|
var obsolete = Date.now() - logBufferObsoleteAfter;
|
|
|
|
var i = liveLogBuffers.length;
|
|
|
|
while ( i-- ) {
|
|
|
|
logBuffer = liveLogBuffers[i];
|
|
|
|
if ( logBuffer.lastReadTime < obsolete ) {
|
|
|
|
logBuffer.stop();
|
|
|
|
liveLogBuffers.splice(i, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setTimeout(logBufferJanitor, logBufferJanitorPeriod);
|
|
|
|
};
|
|
|
|
|
|
|
|
// The janitor will look for stale log buffer every 2 minutes.
|
|
|
|
var logBufferJanitorPeriod = 2 * 60 * 1000;
|
|
|
|
|
|
|
|
// After 30 seconds without being read, a buffer will be considered unused, and
|
|
|
|
// thus removed from memory.
|
|
|
|
var logBufferObsoleteAfter = 30 * 1000;
|
|
|
|
|
|
|
|
setTimeout(logBufferJanitor, logBufferJanitorPeriod);
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-09-14 22:20:40 +02:00
|
|
|
// To mitigate memory churning
|
|
|
|
var netFilteringResultCacheEntryJunkyard = [];
|
|
|
|
var netFilteringResultCacheEntryJunkyardMax = 200;
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
var NetFilteringResultCacheEntry = function(result, type) {
|
|
|
|
this.init(result, type);
|
2014-09-14 22:20:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
NetFilteringResultCacheEntry.prototype.init = function(result, type) {
|
2014-10-02 22:45:26 +02:00
|
|
|
this.result = result;
|
|
|
|
this.type = type;
|
2014-09-14 22:20:40 +02:00
|
|
|
this.time = Date.now();
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
NetFilteringResultCacheEntry.prototype.dispose = function() {
|
2014-10-02 22:45:26 +02:00
|
|
|
this.result = '';
|
|
|
|
this.type = '';
|
2014-09-14 22:20:40 +02:00
|
|
|
if ( netFilteringResultCacheEntryJunkyard.length < netFilteringResultCacheEntryJunkyardMax ) {
|
|
|
|
netFilteringResultCacheEntryJunkyard.push(this);
|
2014-07-30 07:05:35 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-09-14 22:20:40 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
NetFilteringResultCacheEntry.factory = function(result, type) {
|
2014-09-14 22:20:40 +02:00
|
|
|
var entry = netFilteringResultCacheEntryJunkyard.pop();
|
|
|
|
if ( entry === undefined ) {
|
2015-01-06 14:01:15 +01:00
|
|
|
entry = new NetFilteringResultCacheEntry(result, type);
|
2014-09-14 22:20:40 +02:00
|
|
|
} else {
|
2015-01-06 14:01:15 +01:00
|
|
|
entry.init(result, type);
|
2014-09-14 22:20:40 +02:00
|
|
|
}
|
|
|
|
return entry;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// To mitigate memory churning
|
|
|
|
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.urls = {};
|
|
|
|
this.count = 0;
|
|
|
|
this.shelfLife = 60 * 1000;
|
2015-01-06 14:01:15 +01:00
|
|
|
this.timer = null;
|
|
|
|
this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this);
|
2014-09-14 22:20:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
NetFilteringResultCache.prototype.dispose = function() {
|
2015-01-22 03:46:11 +01:00
|
|
|
this.empty();
|
2015-01-06 14:44:19 +01:00
|
|
|
this.boundPruneAsyncCallback = null;
|
2014-09-14 22:20:40 +02:00
|
|
|
if ( netFilteringCacheJunkyard.length < netFilteringCacheJunkyardMax ) {
|
|
|
|
netFilteringCacheJunkyard.push(this);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
NetFilteringResultCache.prototype.add = function(context, result) {
|
|
|
|
var url = context.requestURL;
|
|
|
|
var type = context.requestType;
|
2014-09-14 22:20:40 +02:00
|
|
|
var entry = this.urls[url];
|
|
|
|
if ( entry !== undefined ) {
|
2014-10-02 22:45:26 +02:00
|
|
|
entry.result = result;
|
|
|
|
entry.type = type;
|
2014-09-14 22:20:40 +02:00
|
|
|
entry.time = Date.now();
|
|
|
|
return;
|
|
|
|
}
|
2015-01-06 14:01:15 +01:00
|
|
|
this.urls[url] = NetFilteringResultCacheEntry.factory(result, type);
|
2014-09-14 22:20:40 +02:00
|
|
|
if ( this.count === 0 ) {
|
|
|
|
this.pruneAsync();
|
|
|
|
}
|
|
|
|
this.count += 1;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-22 03:46:11 +01:00
|
|
|
NetFilteringResultCache.prototype.empty = function() {
|
|
|
|
for ( var key in this.urls ) {
|
|
|
|
if ( this.urls.hasOwnProperty(key) === false ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this.urls[key].dispose();
|
|
|
|
}
|
|
|
|
this.urls = {};
|
|
|
|
this.count = 0;
|
|
|
|
if ( this.timer !== null ) {
|
|
|
|
clearTimeout(this.timer);
|
|
|
|
this.timer = null;
|
|
|
|
}
|
2014-09-14 22:20:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
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 ) {
|
2014-07-30 07:05:35 +02:00
|
|
|
break;
|
|
|
|
}
|
2014-09-14 22:20:40 +02:00
|
|
|
entry.dispose();
|
|
|
|
delete this.urls[key];
|
|
|
|
}
|
|
|
|
this.count -= keys.length - i - 1;
|
|
|
|
if ( this.count > 0 ) {
|
|
|
|
this.pruneAsync();
|
2014-07-30 07:05:35 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-10-02 22:45:26 +02:00
|
|
|
// https://www.youtube.com/watch?v=hcVpbsDyOhM
|
2014-09-14 22:20:40 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
NetFilteringResultCache.prototype.pruneAsync = function() {
|
2015-01-06 14:01:15 +01:00
|
|
|
if ( this.timer === null ) {
|
|
|
|
this.timer = setTimeout(this.boundPruneAsyncCallback, this.shelfLife * 2);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
NetFilteringResultCache.prototype.pruneAsyncCallback = function() {
|
|
|
|
this.timer = null;
|
|
|
|
this.prune();
|
2014-09-14 22:20:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-03-31 13:26:43 +02:00
|
|
|
NetFilteringResultCache.prototype.lookup = function(context) {
|
|
|
|
return this.urls[context.requestType + ' ' + context.requestURL];
|
2014-09-14 22:20:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// To mitigate memory churning
|
|
|
|
var frameStoreJunkyard = [];
|
|
|
|
var frameStoreJunkyardMax = 50;
|
|
|
|
|
2014-07-30 07:05:35 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-10-07 04:40:25 +02:00
|
|
|
var FrameStore = function(rootHostname, frameURL) {
|
|
|
|
this.init(rootHostname, frameURL);
|
2014-07-30 07:05:35 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-10-07 04:40:25 +02:00
|
|
|
FrameStore.factory = function(rootHostname, frameURL) {
|
2014-09-14 22:20:40 +02:00
|
|
|
var entry = frameStoreJunkyard.pop();
|
|
|
|
if ( entry === undefined ) {
|
2014-10-07 04:40:25 +02:00
|
|
|
entry = new FrameStore(rootHostname, frameURL);
|
2014-09-14 22:20:40 +02:00
|
|
|
} else {
|
2014-10-07 04:40:25 +02:00
|
|
|
entry.init(rootHostname, frameURL);
|
2014-09-14 22:20:40 +02:00
|
|
|
}
|
|
|
|
return entry;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-10-07 04:40:25 +02:00
|
|
|
FrameStore.prototype.init = function(rootHostname, frameURL) {
|
2014-07-30 07:05:35 +02:00
|
|
|
var µburi = µb.URI;
|
|
|
|
this.pageHostname = µburi.hostnameFromURI(frameURL);
|
2014-10-07 22:30:40 +02:00
|
|
|
this.pageDomain = µburi.domainFromHostname(this.pageHostname) || this.pageHostname;
|
2014-10-07 04:40:25 +02:00
|
|
|
this.rootHostname = rootHostname;
|
2014-10-07 22:30:40 +02:00
|
|
|
this.rootDomain = µburi.domainFromHostname(rootHostname) || rootHostname;
|
2014-12-28 16:07:43 +01:00
|
|
|
// This is part of the filtering evaluation context
|
|
|
|
this.requestURL = this.requestHostname = this.requestType = '';
|
|
|
|
|
2014-07-30 07:05:35 +02:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
FrameStore.prototype.dispose = function() {
|
2014-10-07 22:30:40 +02:00
|
|
|
this.pageHostname = this.pageDomain =
|
2014-12-28 16:07:43 +01:00
|
|
|
this.rootHostname = this.rootDomain =
|
|
|
|
this.requestURL = this.requestHostname = this.requestType = '';
|
2014-09-14 22:20:40 +02:00
|
|
|
if ( frameStoreJunkyard.length < frameStoreJunkyardMax ) {
|
|
|
|
frameStoreJunkyard.push(this);
|
|
|
|
}
|
|
|
|
return null;
|
2014-07-30 07:05:35 +02:00
|
|
|
};
|
|
|
|
|
2014-09-14 22:20:40 +02:00
|
|
|
/******************************************************************************/
|
2014-07-30 07:05:35 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-09-14 22:20:40 +02:00
|
|
|
// To mitigate memory churning
|
|
|
|
var pageStoreJunkyard = [];
|
|
|
|
var pageStoreJunkyardMax = 10;
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-03-23 20:19:17 +01:00
|
|
|
var PageStore = function(tabId, rawURL, pageURL) {
|
|
|
|
this.init(tabId, rawURL, pageURL);
|
2014-07-30 07:05:35 +02:00
|
|
|
};
|
2014-06-24 00:42:43 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-03-23 20:19:17 +01:00
|
|
|
PageStore.factory = function(tabId, rawURL, pageURL) {
|
2014-09-14 22:20:40 +02:00
|
|
|
var entry = pageStoreJunkyard.pop();
|
|
|
|
if ( entry === undefined ) {
|
2015-03-23 20:19:17 +01:00
|
|
|
entry = new PageStore(tabId, rawURL, pageURL);
|
2014-09-14 22:20:40 +02:00
|
|
|
} else {
|
2015-03-23 20:19:17 +01:00
|
|
|
entry.init(tabId, rawURL, pageURL);
|
2014-09-14 22:20:40 +02:00
|
|
|
}
|
|
|
|
return entry;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-03-23 20:19:17 +01:00
|
|
|
PageStore.prototype.init = function(tabId, rawURL, pageURL) {
|
2014-06-24 00:42:43 +02:00
|
|
|
this.tabId = tabId;
|
2015-03-23 20:19:17 +01:00
|
|
|
this.rawURL = rawURL;
|
2014-07-14 17:24:59 +02:00
|
|
|
this.pageURL = pageURL;
|
|
|
|
this.pageHostname = µb.URI.hostnameFromURI(pageURL);
|
2014-08-27 08:01:10 +02:00
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/185
|
2014-08-27 08:01:10 +02:00
|
|
|
// Use hostname if no domain can be extracted
|
|
|
|
this.pageDomain = µb.URI.domainFromHostname(this.pageHostname) || this.pageHostname;
|
2014-10-07 04:40:25 +02:00
|
|
|
this.rootHostname = this.pageHostname;
|
2014-10-07 22:30:40 +02:00
|
|
|
this.rootDomain = this.pageDomain;
|
2014-08-27 08:01:10 +02:00
|
|
|
|
2014-12-28 16:07:43 +01:00
|
|
|
// This is part of the filtering evaluation context
|
|
|
|
this.requestURL = this.requestHostname = this.requestType = '';
|
|
|
|
|
2014-12-30 22:36:29 +01:00
|
|
|
this.hostnameToCountMap = {};
|
2015-01-10 17:23:28 +01:00
|
|
|
this.contentLastModified = 0;
|
2014-09-14 22:20:40 +02:00
|
|
|
this.frames = {};
|
2014-08-02 17:40:27 +02:00
|
|
|
this.netFiltering = true;
|
|
|
|
this.netFilteringReadTime = 0;
|
2014-07-14 17:24:59 +02:00
|
|
|
this.perLoadBlockedRequestCount = 0;
|
|
|
|
this.perLoadAllowedRequestCount = 0;
|
2015-04-05 18:03:14 +02:00
|
|
|
this.hiddenElementCount = ''; // Empty string means "unknown"
|
2014-11-29 21:26:01 +01:00
|
|
|
this.skipLocalMirroring = false;
|
2014-09-14 22:20:40 +02:00
|
|
|
this.netFilteringCache = NetFilteringResultCache.factory();
|
2015-01-06 14:01:15 +01:00
|
|
|
|
2015-01-09 03:04:48 +01:00
|
|
|
// Support `elemhide` filter option. Called at this point so the required
|
|
|
|
// context is all setup at this point.
|
|
|
|
this.skipCosmeticFiltering = µb.staticNetFilteringEngine
|
|
|
|
.matchStringExactType(this, pageURL, 'cosmetic-filtering')
|
|
|
|
.charAt(1) === 'b';
|
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
// Preserve old buffer if there is one already, it may be in use, and
|
|
|
|
// overwritting it would required another read to restart it.
|
|
|
|
if ( this.logBuffer instanceof LogBuffer === false ) {
|
|
|
|
this.logBuffer = logBufferFactory();
|
2014-09-14 22:20:40 +02:00
|
|
|
}
|
|
|
|
|
2014-07-14 17:24:59 +02:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-03-23 20:19:17 +01:00
|
|
|
PageStore.prototype.reuse = function(rawURL, pageURL, context) {
|
2015-03-22 14:30:41 +01:00
|
|
|
// We can't do this: when force refreshing a page, the page store data
|
|
|
|
// needs to be reset
|
|
|
|
//if ( pageURL === this.pageURL ) {
|
|
|
|
// return this;
|
|
|
|
//}
|
2015-03-22 01:30:00 +01:00
|
|
|
|
2015-03-26 00:28:22 +01:00
|
|
|
// If the hostname changes, we can't merely just update the context.
|
|
|
|
var pageHostname = µb.URI.hostnameFromURI(pageURL);
|
|
|
|
if ( pageHostname !== this.pageHostname ) {
|
|
|
|
context = '';
|
|
|
|
}
|
|
|
|
|
2014-10-17 21:44:19 +02:00
|
|
|
// If URL changes without a page reload (more and more common), then we
|
2014-10-02 22:45:26 +02:00
|
|
|
// 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' ) {
|
2015-03-23 20:19:17 +01:00
|
|
|
this.rawURL = rawURL;
|
2014-10-02 22:45:26 +02:00
|
|
|
this.pageURL = pageURL;
|
2014-12-08 18:37:35 +01:00
|
|
|
|
2015-04-07 03:26:05 +02:00
|
|
|
// As part of https://github.com/chrisaljoudi/uBlock/issues/405
|
2014-12-08 18:37:35 +01:00
|
|
|
// URL changed, force a re-evaluation of filtering switch
|
|
|
|
this.netFilteringReadTime = 0;
|
|
|
|
|
2014-10-02 22:45:26 +02:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
// A new page is completely reloaded from scratch, reset all.
|
2014-09-14 22:20:40 +02:00
|
|
|
this.disposeFrameStores();
|
|
|
|
this.netFilteringCache = this.netFilteringCache.dispose();
|
2015-03-23 20:19:17 +01:00
|
|
|
this.init(this.tabId, rawURL, pageURL);
|
2014-06-24 00:42:43 +02:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
2014-09-14 22:20:40 +02:00
|
|
|
// https://www.youtube.com/watch?v=dltNSbOupgE
|
|
|
|
|
2014-06-24 00:42:43 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
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).
|
2015-03-23 20:19:17 +01:00
|
|
|
this.rawURL = this.pageURL =
|
2014-10-17 21:44:19 +02:00
|
|
|
this.pageHostname = this.pageDomain =
|
2014-12-28 16:07:43 +01:00
|
|
|
this.rootHostname = this.rootDomain =
|
|
|
|
this.requestURL = this.requestHostname = this.requestType = '';
|
2014-12-30 22:36:29 +01:00
|
|
|
this.hostnameToCountMap = null;
|
2014-09-14 22:20:40 +02:00
|
|
|
this.disposeFrameStores();
|
|
|
|
this.netFilteringCache = this.netFilteringCache.dispose();
|
2015-01-06 14:01:15 +01:00
|
|
|
this.logBuffer = this.logBuffer.dispose();
|
2014-09-14 22:20:40 +02:00
|
|
|
if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) {
|
2014-06-24 00:42:43 +02:00
|
|
|
pageStoreJunkyard.push(this);
|
|
|
|
}
|
2014-09-14 22:20:40 +02:00
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
PageStore.prototype.disposeFrameStores = function() {
|
|
|
|
var frames = this.frames;
|
2014-12-28 16:07:43 +01:00
|
|
|
for ( var k in frames ) {
|
|
|
|
if ( frames.hasOwnProperty(k) ) {
|
2014-09-14 22:20:40 +02:00
|
|
|
frames[k].dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.frames = {};
|
2014-06-24 00:42:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-02-25 20:15:36 +01:00
|
|
|
PageStore.prototype.getFrame = function(frameId) {
|
2015-03-02 16:41:51 +01:00
|
|
|
return this.frames[frameId] || null;
|
2014-07-30 07:05:35 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-02-25 20:15:36 +01:00
|
|
|
PageStore.prototype.setFrame = function(frameId, frameURL) {
|
|
|
|
var frameStore = this.frames[frameId];
|
|
|
|
if ( frameStore instanceof FrameStore ) {
|
|
|
|
frameStore.init(this.rootHostname, frameURL);
|
|
|
|
} else {
|
|
|
|
this.frames[frameId] = FrameStore.factory(this.rootHostname, frameURL);
|
|
|
|
}
|
2014-07-30 07:05:35 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-08-02 17:40:27 +02:00
|
|
|
PageStore.prototype.getNetFilteringSwitch = function() {
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/1078
|
2015-03-23 20:19:17 +01:00
|
|
|
// Use both the raw and normalized URLs.
|
2014-08-02 17:40:27 +02:00
|
|
|
if ( this.netFilteringReadTime < µb.netWhitelistModifyTime ) {
|
2014-12-14 23:21:59 +01:00
|
|
|
this.netFiltering = µb.getNetFilteringSwitch(this.pageURL);
|
2015-03-23 20:19:17 +01:00
|
|
|
if ( this.netFiltering && this.rawURL !== this.pageURL ) {
|
|
|
|
this.netFiltering = µb.getNetFilteringSwitch(this.rawURL);
|
|
|
|
}
|
2014-08-02 17:40:27 +02:00
|
|
|
this.netFilteringReadTime = Date.now();
|
|
|
|
}
|
|
|
|
return this.netFiltering;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-02-06 05:14:12 +01:00
|
|
|
PageStore.prototype.getSpecificCosmeticFilteringSwitch = function() {
|
|
|
|
return this.getNetFilteringSwitch() &&
|
2015-04-05 18:03:14 +02:00
|
|
|
µb.hnSwitches.evaluateZ('noCosmeticFiltering', this.rootHostname) !== true &&
|
|
|
|
(µb.userSettings.advancedUserEnabled &&
|
|
|
|
µb.sessionFirewall.mustAllowCellZY(this.rootHostname, this.rootHostname, '*')) === false;
|
2015-02-06 05:14:12 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
PageStore.prototype.getGenericCosmeticFilteringSwitch = function() {
|
|
|
|
return this.getNetFilteringSwitch() &&
|
|
|
|
this.skipCosmeticFiltering === false &&
|
2015-04-05 18:03:14 +02:00
|
|
|
µb.hnSwitches.evaluateZ('noCosmeticFiltering', this.rootHostname) !== true &&
|
|
|
|
(µb.userSettings.advancedUserEnabled &&
|
|
|
|
µb.sessionFirewall.mustAllowCellZY(this.rootHostname, this.rootHostname, '*')) === false;
|
2015-01-09 03:04:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-22 03:46:11 +01:00
|
|
|
PageStore.prototype.toggleNetFilteringSwitch = function(url, scope, state) {
|
|
|
|
µb.toggleNetFilteringSwitch(url, scope, state);
|
|
|
|
this.netFilteringCache.empty();
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-12-28 16:07:43 +01:00
|
|
|
PageStore.prototype.filterRequest = function(context) {
|
2015-01-24 18:06:22 +01:00
|
|
|
|
2014-12-28 16:07:43 +01:00
|
|
|
if ( this.getNetFilteringSwitch() === false ) {
|
2015-01-24 18:06:22 +01:00
|
|
|
if ( collapsibleRequestTypes.indexOf(context.requestType) !== -1 ) {
|
|
|
|
this.netFilteringCache.add(context, '');
|
|
|
|
}
|
2014-12-28 16:07:43 +01:00
|
|
|
return '';
|
2014-06-24 00:42:43 +02:00
|
|
|
}
|
2014-12-28 16:07:43 +01:00
|
|
|
|
2015-03-31 13:26:43 +02:00
|
|
|
var entry = this.netFilteringCache.lookup(context);
|
2014-12-28 16:07:43 +01:00
|
|
|
if ( entry !== undefined ) {
|
2015-01-06 14:01:15 +01:00
|
|
|
//console.debug('cache HIT: PageStore.filterRequest("%s")', context.requestURL);
|
2014-12-28 16:07:43 +01:00
|
|
|
return entry.result;
|
2014-09-14 22:20:40 +02:00
|
|
|
}
|
2014-12-28 16:07:43 +01:00
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
var result = '';
|
|
|
|
|
|
|
|
// Given that:
|
|
|
|
// - Dynamic filtering override static filtering
|
|
|
|
// - Evaluating dynamic filtering is much faster than static filtering
|
|
|
|
// We evaluate dynamic filtering first, and hopefully we can skip
|
|
|
|
// evaluation of static filtering.
|
2015-01-06 17:44:06 +01:00
|
|
|
if ( µb.userSettings.advancedUserEnabled ) {
|
2015-02-11 06:26:45 +01:00
|
|
|
var df = µb.sessionFirewall.evaluateCellZY(context.rootHostname, context.requestHostname, context.requestType);
|
2015-01-06 17:44:06 +01:00
|
|
|
if ( df.mustBlockOrAllow() ) {
|
|
|
|
result = df.toFilterString();
|
|
|
|
}
|
2015-01-06 14:01:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Static filtering never override dynamic filtering
|
|
|
|
if ( result === '' ) {
|
|
|
|
result = µb.staticNetFilteringEngine.matchString(context);
|
|
|
|
}
|
|
|
|
|
|
|
|
//console.debug('cache MISS: PageStore.filterRequest("%s")', context.requestURL);
|
2015-01-24 18:06:22 +01:00
|
|
|
if ( collapsibleRequestTypes.indexOf(context.requestType) !== -1 ) {
|
|
|
|
this.netFilteringCache.add(context, result);
|
|
|
|
}
|
2014-12-28 16:07:43 +01:00
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
// console.debug('[%s, %s] = "%s"', context.requestHostname, context.requestType, result);
|
2014-12-28 16:07:43 +01:00
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2015-01-24 18:06:22 +01:00
|
|
|
// Cache only what is worth it if logging is disabled
|
|
|
|
// http://jsperf.com/string-indexof-vs-object
|
|
|
|
var collapsibleRequestTypes = 'image sub_frame object';
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
PageStore.prototype.filterRequestNoCache = function(context) {
|
|
|
|
|
2015-04-08 03:26:13 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/pull/1209
|
|
|
|
// Not ideal, but until something better is figured, this solves the issue.
|
2015-04-08 03:58:19 +02:00
|
|
|
// Long term, I have some ideas I would like to test to take care of those
|
|
|
|
// cases for which a page store may not have been definitely bound to a tab.
|
|
|
|
if ( context.overrideStore ) {
|
|
|
|
if ( µb.getNetFilteringSwitch(context.requestURL) === false ) {
|
2015-04-08 03:26:13 +02:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
} else if ( this.getNetFilteringSwitch() === false ) {
|
2015-01-24 18:06:22 +01:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
var result = '';
|
|
|
|
|
|
|
|
// Given that:
|
|
|
|
// - Dynamic filtering override static filtering
|
|
|
|
// - Evaluating dynamic filtering is much faster than static filtering
|
|
|
|
// We evaluate dynamic filtering first, and hopefully we can skip
|
|
|
|
// evaluation of static filtering.
|
|
|
|
if ( µb.userSettings.advancedUserEnabled ) {
|
2015-02-11 06:26:45 +01:00
|
|
|
var df = µb.sessionFirewall.evaluateCellZY(context.rootHostname, context.requestHostname, context.requestType);
|
2015-01-24 18:06:22 +01:00
|
|
|
if ( df.mustBlockOrAllow() ) {
|
|
|
|
result = df.toFilterString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Static filtering never override dynamic filtering
|
|
|
|
if ( result === '' ) {
|
|
|
|
result = µb.staticNetFilteringEngine.matchString(context);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2015-01-06 14:01:15 +01:00
|
|
|
/******************************************************************************/
|
2014-12-31 16:47:19 +01:00
|
|
|
|
2015-01-24 18:06:22 +01:00
|
|
|
PageStore.prototype.logRequest = function(context, result) {
|
2014-12-28 16:07:43 +01:00
|
|
|
var requestHostname = context.requestHostname;
|
2015-02-07 00:11:36 +01:00
|
|
|
// rhill 20150206:
|
|
|
|
// be prepared to handle invalid requestHostname, I've seen this
|
|
|
|
// happen: http://./
|
|
|
|
if ( requestHostname === '' ) {
|
|
|
|
requestHostname = context.pageHostname;
|
|
|
|
}
|
2015-03-05 01:38:19 +01:00
|
|
|
var now = Date.now();
|
2014-12-30 22:36:29 +01:00
|
|
|
if ( this.hostnameToCountMap.hasOwnProperty(requestHostname) === false ) {
|
|
|
|
this.hostnameToCountMap[requestHostname] = 0;
|
2015-03-05 01:38:19 +01:00
|
|
|
this.contentLastModified = now;
|
2014-12-30 22:36:29 +01:00
|
|
|
}
|
|
|
|
var c = result.charAt(1);
|
|
|
|
if ( c === '' || c === 'a' ) {
|
|
|
|
this.hostnameToCountMap[requestHostname] += 0x00010000;
|
2015-01-24 18:06:22 +01:00
|
|
|
this.perLoadAllowedRequestCount++;
|
|
|
|
µb.localSettings.allowedRequestCount++;
|
2014-12-30 22:36:29 +01:00
|
|
|
} else /* if ( c === 'b' ) */ {
|
|
|
|
this.hostnameToCountMap[requestHostname] += 0x00000001;
|
2015-01-24 18:06:22 +01:00
|
|
|
this.perLoadBlockedRequestCount++;
|
|
|
|
µb.localSettings.blockedRequestCount++;
|
2014-12-28 16:07:43 +01:00
|
|
|
}
|
2015-03-06 03:17:09 +01:00
|
|
|
µb.localSettingsModifyTime = now;
|
2015-01-24 18:06:22 +01:00
|
|
|
this.logBuffer.writeOne(context, result);
|
2014-10-02 22:45:26 +02:00
|
|
|
};
|
|
|
|
|
2014-10-06 20:02:44 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
2015-01-16 18:15:12 +01:00
|
|
|
PageStore.prototype.toMirrorURL = function(requestURL) {
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/351
|
2015-01-16 18:15:12 +01:00
|
|
|
// Bypass experimental features when uBlock is disabled for a site
|
|
|
|
if ( µb.userSettings.experimentalEnabled === false ||
|
|
|
|
this.getNetFilteringSwitch() === false ||
|
|
|
|
this.skipLocalMirroring ) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://code.google.com/p/chromium/issues/detail?id=387198
|
|
|
|
// Not all redirects will succeed, until bug above is fixed.
|
|
|
|
return µb.mirrors.toURL(requestURL, true);
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2014-09-11 15:53:49 +02:00
|
|
|
PageStore.prototype.updateBadge = function() {
|
2014-08-02 17:40:27 +02:00
|
|
|
var netFiltering = this.getNetFilteringSwitch();
|
2014-12-26 21:41:44 +01:00
|
|
|
var badge = '';
|
2014-08-02 17:40:27 +02:00
|
|
|
if ( µb.userSettings.showIconBadge && netFiltering && this.perLoadBlockedRequestCount ) {
|
2014-12-26 21:41:44 +01:00
|
|
|
badge = µb.utils.formatCount(this.perLoadBlockedRequestCount);
|
2014-06-24 00:42:43 +02:00
|
|
|
}
|
2014-12-26 21:41:44 +01:00
|
|
|
vAPI.setIcon(this.tabId, netFiltering ? 'on' : 'off', badge);
|
2014-06-24 00:42:43 +02:00
|
|
|
};
|
|
|
|
|
2014-08-20 02:41:52 +02:00
|
|
|
// https://www.youtube.com/watch?v=drW8p_dTLD4
|
|
|
|
|
2014-06-24 00:42:43 +02:00
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
return {
|
2014-09-14 22:20:40 +02:00
|
|
|
factory: PageStore.factory
|
2014-06-24 00:42:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|