mirror of
https://github.com/gorhill/uBlock.git
synced 2024-09-18 17:02:27 +02:00
local mirroring work
This commit is contained in:
parent
50c169d22e
commit
52538d1e41
@ -93,7 +93,7 @@ return {
|
|||||||
},
|
},
|
||||||
'assets/ublock/privacy.txt': {
|
'assets/ublock/privacy.txt': {
|
||||||
off: true,
|
off: true,
|
||||||
title: 'µBlock filters - Privacy',
|
title: 'µBlock filters – Privacy',
|
||||||
group: 'default'
|
group: 'default'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -529,29 +529,25 @@ var getPageDetails = function(µb, tabId) {
|
|||||||
var µburi = µb.URI;
|
var µburi = µb.URI;
|
||||||
var dict = pageStore.netFilteringCache.fetchAll();
|
var dict = pageStore.netFilteringCache.fetchAll();
|
||||||
var r = [];
|
var r = [];
|
||||||
var details, pos, result, hostname, domain;
|
var details, hostname, domain;
|
||||||
for ( var url in dict ) {
|
for ( var url in dict ) {
|
||||||
if ( dict.hasOwnProperty(url) === false ) {
|
if ( dict.hasOwnProperty(url) === false ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
details = dict[url].data;
|
details = dict[url];
|
||||||
if ( typeof details !== 'string' ) {
|
if ( wantBlocked !== pageStore.boolFromResult(details.result) ) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
pos = details.indexOf('\t');
|
|
||||||
result = details.slice(pos + 1);
|
|
||||||
if ( wantBlocked !== pageStore.boolFromResult(result) ) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
hasher.appendStr(url);
|
hasher.appendStr(url);
|
||||||
hasher.appendStr(details);
|
hasher.appendStr(details.result);
|
||||||
hostname = µburi.hostnameFromURI(url);
|
hostname = µburi.hostnameFromURI(url);
|
||||||
domain = µburi.domainFromHostname(hostname) || hostname;
|
domain = µburi.domainFromHostname(hostname) || hostname;
|
||||||
r.push({
|
r.push({
|
||||||
type: details.slice(0, pos),
|
|
||||||
domain: domain,
|
|
||||||
url: url,
|
url: url,
|
||||||
reason: result
|
domain: domain,
|
||||||
|
reason: details.result,
|
||||||
|
type: details.type,
|
||||||
|
flags: details.flags
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
|
300
js/mirrors.js
300
js/mirrors.js
@ -19,6 +19,7 @@
|
|||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* jshint bitwise: false */
|
||||||
/* global chrome, YaMD5, µBlock */
|
/* global chrome, YaMD5, µBlock */
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -29,7 +30,16 @@
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// To show keys in local storage from console:
|
||||||
|
// chrome.storage.local.get(null, function (data) { console.log(Object.keys(data)) });
|
||||||
|
|
||||||
|
// To cleanup cached items from console:
|
||||||
|
// chrome.storage.local.get(null, function (data) { chrome.storage.local.remove(Object.keys(data).filter(function(a){ return a.indexOf('mirrors_item_') === 0; })); });
|
||||||
|
|
||||||
|
|
||||||
var exports = {
|
var exports = {
|
||||||
|
bytesInUseMax: 5 * 1024 * 1024,
|
||||||
|
ttl: 21 * 24 * 60 * 60 * 1000,
|
||||||
bytesInUse: 0,
|
bytesInUse: 0,
|
||||||
hitCount: 0
|
hitCount: 0
|
||||||
};
|
};
|
||||||
@ -38,6 +48,8 @@ var exports = {
|
|||||||
|
|
||||||
var nullFunc = function() {};
|
var nullFunc = function() {};
|
||||||
|
|
||||||
|
// TODO: need to come up with something better. Key shoud be domain. More
|
||||||
|
// control over what significant part(s) of a URL is to be used as key.
|
||||||
var mirrorCandidates = {
|
var mirrorCandidates = {
|
||||||
'ajax.googleapis.com': /^ajax\.googleapis\.com\/ajax\/libs\//,
|
'ajax.googleapis.com': /^ajax\.googleapis\.com\/ajax\/libs\//,
|
||||||
'fonts.googleapis.com': /^fonts\.googleapis\.com/,
|
'fonts.googleapis.com': /^fonts\.googleapis\.com/,
|
||||||
@ -45,28 +57,35 @@ var mirrorCandidates = {
|
|||||||
'cdnjs.cloudflare.com': /^cdnjs\.cloudflare\.com\/ajax\/libs\//,
|
'cdnjs.cloudflare.com': /^cdnjs\.cloudflare\.com\/ajax\/libs\//,
|
||||||
'code.jquery.com': /^code\.jquery\.com/,
|
'code.jquery.com': /^code\.jquery\.com/,
|
||||||
's0.2mdn.net': /(2mdn\.net\/instream\/html5\/ima3\.js)/,
|
's0.2mdn.net': /(2mdn\.net\/instream\/html5\/ima3\.js)/,
|
||||||
'connect.facebook.net': /(connect\.facebook\.net\/[^\/]+\/all\.js)/,
|
'www.googletagservices.com': /(www\.googletagservices\.com\/tag\/js\/gpt\.js)/,
|
||||||
'www.googletagservices.com': /(www\.googletagservices\.com\/tag\/js\/gpt\.js)/
|
'maxcdn.bootstrapcdn.com': /^maxcdn\.bootstrapcdn\.com\/font-awesome\//,
|
||||||
|
'b.scorecardresearch.com': /^b\.scorecardresearch\.com\/beacon\.js/,
|
||||||
|
'platform.twitter.com': /^platform\.twitter\.com\/widgets\.js/,
|
||||||
|
'cdn.quilt.janrain.com': /^cdn\.quilt\.janrain\.com\//
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var magicId = 'rmwwgwkzcgfv';
|
var magicId = 'rmwwgwkzcgfv';
|
||||||
var bytesInUseMax = 20 * 1024 * 1024;
|
|
||||||
var ttl = 30 * 24 * 60 * 60 * 1000;
|
|
||||||
var metadataPersistTimer = null;
|
var metadataPersistTimer = null;
|
||||||
|
var bytesInUseMercy = 1 * 1024 * 1024;
|
||||||
|
|
||||||
var metadata = {
|
var metadata = {
|
||||||
magicId: magicId,
|
magicId: magicId,
|
||||||
urlKeyToHashMap: {}
|
urlKeyToHashMap: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hash to content map
|
var hashToContentMap = {};
|
||||||
var hashToDataUrlMap = {};
|
|
||||||
|
|
||||||
var loaded = false;
|
var loaded = false;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Ideally, URL keys and access time would be attached to the data URL entry
|
||||||
|
// itself, but then this would mean the need to persist the whole data URL
|
||||||
|
// every time a new URL key is added or the data URL is accessed, and given the
|
||||||
|
// data URL can be quite large, that would make no sense efficiency-wise to
|
||||||
|
// re-persist the whole thing.
|
||||||
|
// So, ContentEntry persisted once, MetadataEntry persisted often.
|
||||||
|
|
||||||
var MetadataEntry = function(hash) {
|
var MetadataEntry = function(hash) {
|
||||||
this.accessTime = Date.now();
|
this.accessTime = Date.now();
|
||||||
this.hash = hash;
|
this.hash = hash;
|
||||||
@ -98,6 +117,64 @@ var getTextFileFromURL = function(url, onLoad, onError) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Safe binary-to-base64. Because window.btoa doesn't work for binary data...
|
||||||
|
//
|
||||||
|
// This implementation doesn't require the creation of a full-length
|
||||||
|
// intermediate buffer. I expect less short-term memory use will translate in
|
||||||
|
// more efficient conversion. Hopefully I will get time to confirm with
|
||||||
|
// benchmarks in the future.
|
||||||
|
|
||||||
|
var btoaMap = (function(){
|
||||||
|
var out = new Uint8Array(64);
|
||||||
|
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
var i = chars.length;
|
||||||
|
while ( i-- ) {
|
||||||
|
out[i] = chars.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
})();
|
||||||
|
|
||||||
|
var btoaSafe = function(input) {
|
||||||
|
var output = [];
|
||||||
|
var bamap = btoaMap;
|
||||||
|
var n = Math.floor(input.length / 3) * 3;
|
||||||
|
var b1, b2, b3;
|
||||||
|
for ( var ii = 0; ii < n; ii += 3 ) {
|
||||||
|
b1 = input.charCodeAt(ii );
|
||||||
|
b2 = input.charCodeAt(ii+1);
|
||||||
|
b3 = input.charCodeAt(ii+2);
|
||||||
|
output.push(String.fromCharCode(
|
||||||
|
bamap[ b1 >>> 2],
|
||||||
|
bamap[(b1 & 0x03) << 4 | b2 >>> 4],
|
||||||
|
bamap[(b2 & 0x0F) << 2 | b3 >>> 6],
|
||||||
|
bamap[ b3 & 0x3F ]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// Leftover
|
||||||
|
var m = input.length - n;
|
||||||
|
if ( m > 1 ) {
|
||||||
|
b1 = input.charCodeAt(ii );
|
||||||
|
b2 = input.charCodeAt(ii+1);
|
||||||
|
output.push(String.fromCharCode(
|
||||||
|
bamap[ b1 >>> 2],
|
||||||
|
bamap[(b1 & 0x03) << 4 | b2 >>> 4],
|
||||||
|
bamap[(b2 & 0x0F) << 2],
|
||||||
|
0x3D
|
||||||
|
));
|
||||||
|
} else if ( m !== 0 ) {
|
||||||
|
b1 = input.charCodeAt(ii);
|
||||||
|
output.push(String.fromCharCode(
|
||||||
|
bamap[ b1 >>>2],
|
||||||
|
bamap[(b1 & 0x03) << 4 ],
|
||||||
|
0x3D,
|
||||||
|
0x3D
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return output.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
// Extract a `key` from a URL.
|
// Extract a `key` from a URL.
|
||||||
|
|
||||||
var toUrlKey = function(url) {
|
var toUrlKey = function(url) {
|
||||||
@ -111,10 +188,10 @@ var toUrlKey = function(url) {
|
|||||||
url = url.slice(pos + 3);
|
url = url.slice(pos + 3);
|
||||||
pos = url.indexOf('/');
|
pos = url.indexOf('/');
|
||||||
if ( pos === -1 ) {
|
if ( pos === -1 ) {
|
||||||
return -1;
|
return '';
|
||||||
}
|
}
|
||||||
var re = mirrorCandidates[url.slice(0, pos)];
|
var re = mirrorCandidates[url.slice(0, pos)];
|
||||||
if ( typeof re !== 'object' || typeof re.test !== 'function' ) {
|
if ( typeof re !== 'object' || typeof re.exec !== 'function' ) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
var matches = re.exec(url);
|
var matches = re.exec(url);
|
||||||
@ -148,13 +225,84 @@ var normalizeContentType = function(ctin) {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var metadataExists = function(urlKey) {
|
var metadataExists = function(urlKey) {
|
||||||
return typeof urlKey === 'string' && metadata.urlKeyToHashMap.hasOwnProperty(urlKey);
|
return typeof urlKey === 'string' &&
|
||||||
|
metadata.urlKeyToHashMap.hasOwnProperty(urlKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var contentExists = function(hash) {
|
var contentExists = function(hash) {
|
||||||
return typeof hash === 'string' && hashToDataUrlMap.hasOwnProperty(hash);
|
return typeof hash === 'string' &&
|
||||||
|
hashToContentMap.hasOwnProperty(hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var storageKeyFromHash = function(hash) {
|
||||||
|
return 'mirrors_item_' + hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Given that a single data URL can be shared by many URL keys, pruning is a
|
||||||
|
// bit hairy. So the steps are:
|
||||||
|
// - Collate information about each data URL:
|
||||||
|
// - Last time they were used
|
||||||
|
// - Which URL keys reference them
|
||||||
|
// This will allow us to flush from memory the ones least recently used first.
|
||||||
|
|
||||||
|
var pruneToSize = function(toSize) {
|
||||||
|
if ( exports.bytesInUse < toSize ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var k2hMap = metadata.urlKeyToHashMap;
|
||||||
|
var h2cMap = hashToContentMap;
|
||||||
|
var urlKey, hash;
|
||||||
|
var mdEntry, ctEntry, prEntry;
|
||||||
|
var pruneMap = {};
|
||||||
|
for ( urlKey in k2hMap ) {
|
||||||
|
if ( k2hMap.hasOwnProperty(urlKey) === false ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mdEntry = k2hMap[urlKey];
|
||||||
|
hash = mdEntry.hash;
|
||||||
|
if ( pruneMap.hasOwnProperty(hash) === false ) {
|
||||||
|
pruneMap[hash] = {
|
||||||
|
urlKeys: [urlKey],
|
||||||
|
accessTime: mdEntry.accessTime
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
prEntry = pruneMap[hash];
|
||||||
|
prEntry.urlKeys.push(urlKey);
|
||||||
|
prEntry.accessTime = Math.max(prEntry.accessTime, mdEntry.accessTime);
|
||||||
|
}
|
||||||
|
// Least recent at the end of array
|
||||||
|
var compare = function(a, b) {
|
||||||
|
return pruneMap[b].accessTime - pruneMap[a].accessTime;
|
||||||
|
};
|
||||||
|
var hashes = Object.keys(pruneMap).sort(compare);
|
||||||
|
var toRemove = [];
|
||||||
|
var i = hashes.length;
|
||||||
|
while ( i-- ) {
|
||||||
|
hash = hashes[i];
|
||||||
|
prEntry = pruneMap[hash];
|
||||||
|
ctEntry = h2cMap[hash];
|
||||||
|
delete h2cMap[hash];
|
||||||
|
toRemove.push(storageKeyFromHash(hash));
|
||||||
|
exports.bytesInUse -= ctEntry.dataURL.length;
|
||||||
|
while ( urlKey = prEntry.urlKeys.pop() ) {
|
||||||
|
delete k2hMap[urlKey];
|
||||||
|
}
|
||||||
|
if ( exports.bytesInUse < toSize ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( toRemove.length !== 0 ) {
|
||||||
|
//console.debug('mirrors.pruneToSize(%d): removing %o', toSize, toRemove);
|
||||||
|
removeContent(toRemove);
|
||||||
|
updateMetadataNow();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -166,16 +314,7 @@ var updateMetadata = function() {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var updateMetadataAsync = function(urlKey, hash) {
|
var updateMetadataNow = function() {
|
||||||
var doesExist = metadataExists(urlKey);
|
|
||||||
if ( doesExist ) {
|
|
||||||
metadata.urlKeyToHashMap[urlKey].accessTime = Date.now();
|
|
||||||
if ( metadataPersistTimer === null ) {
|
|
||||||
setTimeout(updateMetadata, 60 * 1000);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
metadata.urlKeyToHashMap[urlKey] = new MetadataEntry(hash);
|
|
||||||
if ( metadataPersistTimer !== null ) {
|
if ( metadataPersistTimer !== null ) {
|
||||||
clearTimeout(metadataPersistTimer);
|
clearTimeout(metadataPersistTimer);
|
||||||
}
|
}
|
||||||
@ -184,16 +323,45 @@ var updateMetadataAsync = function(urlKey, hash) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var updateContent = function(hash, dataURL) {
|
var updateMetadataAsync = function() {
|
||||||
if ( contentExists(hash) !== false ) {
|
if ( metadataPersistTimer === null ) {
|
||||||
|
setTimeout(updateMetadata, 60 * 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var addMetadata = function(urlKey, hash) {
|
||||||
|
metadata.urlKeyToHashMap[urlKey] = new MetadataEntry(hash);
|
||||||
|
updateMetadataNow();
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var removeMetadata = function(urlKey) {
|
||||||
|
delete metadata.urlKeyToHashMap[urlKey];
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var addContent = function(hash, dataURL) {
|
||||||
|
if ( contentExists(hash) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var contentEntry = hashToDataUrlMap[hash] = new ContentEntry(dataURL);
|
var contentEntry = hashToContentMap[hash] = new ContentEntry(dataURL);
|
||||||
exports.bytesInUse += dataURL.length;
|
exports.bytesInUse += dataURL.length;
|
||||||
var key = 'mirrors_item_' + hash;
|
|
||||||
var bin = {};
|
var bin = {};
|
||||||
bin[key] = contentEntry;
|
bin[storageKeyFromHash(hash)] = contentEntry;
|
||||||
chrome.storage.local.set(bin);
|
chrome.storage.local.set(bin);
|
||||||
|
if ( exports.bytesInUse >= exports.bytesInUseMax + bytesInUseMercy ) {
|
||||||
|
pruneToSize(exports.bytesInUseMax);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var removeContent = function(what) {
|
||||||
|
chrome.storage.local.remove(what);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -218,7 +386,7 @@ var cacheAsset = function(url) {
|
|||||||
yamd5.appendAsciiStr(contentType);
|
yamd5.appendAsciiStr(contentType);
|
||||||
yamd5.appendAsciiStr(this.response);
|
yamd5.appendAsciiStr(this.response);
|
||||||
var hash = yamd5.end();
|
var hash = yamd5.end();
|
||||||
updateMetadataAsync(urlKey, hash);
|
addMetadata(urlKey, hash);
|
||||||
if ( contentExists(hash) ) {
|
if ( contentExists(hash) ) {
|
||||||
//console.debug('mirrors.cacheAsset(): reusing existing content for "%s"', urlKey);
|
//console.debug('mirrors.cacheAsset(): reusing existing content for "%s"', urlKey);
|
||||||
return;
|
return;
|
||||||
@ -226,10 +394,18 @@ var cacheAsset = function(url) {
|
|||||||
//console.debug('mirrors.cacheAsset(): caching new content for "%s"', urlKey);
|
//console.debug('mirrors.cacheAsset(): caching new content for "%s"', urlKey);
|
||||||
// Keep original encoding if there was one, otherwise use base64 --
|
// Keep original encoding if there was one, otherwise use base64 --
|
||||||
// as the result is somewhat more compact I believe
|
// as the result is somewhat more compact I believe
|
||||||
var dataUrl = contentType.indexOf(';') !== -1 ?
|
var dataUrl = null;
|
||||||
|
try {
|
||||||
|
dataUrl = contentType.indexOf(';') !== -1 ?
|
||||||
'data:' + contentType + ',' + encodeURIComponent(this.responseText) :
|
'data:' + contentType + ',' + encodeURIComponent(this.responseText) :
|
||||||
'data:' + contentType + ';base64,' + btoa(this.response);
|
'data:' + contentType + ';base64,' + btoa(this.response);
|
||||||
updateContent(hash, dataUrl);
|
} catch (e) {
|
||||||
|
//console.debug(e);
|
||||||
|
}
|
||||||
|
if ( dataUrl === null ) {
|
||||||
|
dataUrl = 'data:' + contentType + ';base64,' + btoaSafe(this.response);
|
||||||
|
}
|
||||||
|
addContent(hash, dataUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
var onRemoteAssetError = function() {
|
var onRemoteAssetError = function() {
|
||||||
@ -256,14 +432,18 @@ var toURL = function(url, cache) {
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
var dataURL = '';
|
||||||
var metadataEntry = metadata.urlKeyToHashMap[urlKey];
|
var metadataEntry = metadata.urlKeyToHashMap[urlKey];
|
||||||
if ( contentExists(metadataEntry.hash) === false ) {
|
if ( contentExists(metadataEntry.hash) ) {
|
||||||
return '';
|
dataURL = hashToContentMap[metadataEntry.hash].dataURL;
|
||||||
}
|
metadataEntry.accessTime = Date.now();
|
||||||
var contentEntry = hashToDataUrlMap[metadataEntry.hash];
|
|
||||||
updateMetadataAsync(urlKey);
|
|
||||||
exports.hitCount += 1;
|
exports.hitCount += 1;
|
||||||
return contentEntry.dataURL;
|
} else {
|
||||||
|
//console.debug('mirrors.toURL(): content not found "%s"', url);
|
||||||
|
delete metadata.urlKeyToHashMap[urlKey];
|
||||||
|
}
|
||||||
|
updateMetadataAsync();
|
||||||
|
return dataURL;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -271,32 +451,43 @@ var toURL = function(url, cache) {
|
|||||||
var load = function() {
|
var load = function() {
|
||||||
loaded = true;
|
loaded = true;
|
||||||
|
|
||||||
var loadContent = function(hash) {
|
var loadContent = function(urlKey, hash) {
|
||||||
var key = 'mirrors_item_' + hash;
|
var binKey = storageKeyFromHash(hash);
|
||||||
var onContentReady = function(bin) {
|
var onContentReady = function(bin) {
|
||||||
if ( chrome.runtime.lastError ) {
|
if ( chrome.runtime.lastError || bin.hasOwnProperty(binKey) === false ) {
|
||||||
|
//console.debug('mirrors.load(): failed to load content "%s"', binKey);
|
||||||
|
removeMetadata(urlKey);
|
||||||
|
removeContent(binKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var contentEntry = bin[key];
|
//console.debug('mirrors.load(): loaded content "%s"', binKey);
|
||||||
hashToDataUrlMap[hash] = contentEntry;
|
var ctEntry = hashToContentMap[hash] = bin[binKey];
|
||||||
exports.bytesInUse += contentEntry.dataURL.length;
|
exports.bytesInUse += ctEntry.dataURL.length;
|
||||||
};
|
};
|
||||||
var bin = {};
|
chrome.storage.local.get(binKey, onContentReady);
|
||||||
bin[key] = '';
|
|
||||||
chrome.storage.local.get(bin, onContentReady);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var onMetadataReady = function(bin) {
|
var onMetadataReady = function(bin) {
|
||||||
if ( chrome.runtime.lastError ) {
|
//console.debug('mirrors.load(): loaded metadata');
|
||||||
return;
|
|
||||||
}
|
|
||||||
metadata = bin.mirrors_metadata;
|
metadata = bin.mirrors_metadata;
|
||||||
var hemap = metadata.urlKeyToHashMap;
|
var toRemove = [];
|
||||||
for ( var urlKey in hemap ) {
|
var u2hmap = metadata.urlKeyToHashMap;
|
||||||
if ( hemap.hasOwnProperty(urlKey) === false ) {
|
var hash;
|
||||||
|
for ( var urlKey in u2hmap ) {
|
||||||
|
if ( u2hmap.hasOwnProperty(urlKey) === false ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
loadContent(hemap[urlKey].hash);
|
hash = u2hmap[urlKey].hash;
|
||||||
|
if ( metadata.magicId !== magicId ) {
|
||||||
|
toRemove.push(storageKeyFromHash(hash));
|
||||||
|
removeMetadata(urlKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
loadContent(urlKey, hash);
|
||||||
|
}
|
||||||
|
if ( toRemove.length !== 0 ) {
|
||||||
|
removeContent(toRemove);
|
||||||
|
updateMetadataNow();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -306,11 +497,9 @@ var load = function() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var unload = function() {
|
var unload = function() {
|
||||||
if ( metadataPersistTimer !== null ) {
|
updateMetadataNow();
|
||||||
updateMetadata();
|
|
||||||
}
|
|
||||||
metadata.urlKeyToHashMap = {};
|
metadata.urlKeyToHashMap = {};
|
||||||
hashToDataUrlMap = {};
|
hashToContentMap = {};
|
||||||
exports.bytesInUse = 0;
|
exports.bytesInUse = 0;
|
||||||
exports.hitCount = 0;
|
exports.hitCount = 0;
|
||||||
|
|
||||||
@ -332,6 +521,7 @@ exports.toggle = function(on) {
|
|||||||
// Export API
|
// Export API
|
||||||
|
|
||||||
exports.toURL = toURL;
|
exports.toURL = toURL;
|
||||||
|
exports.pruneToSize = pruneToSize;
|
||||||
|
|
||||||
return exports;
|
return exports;
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* jshint bitwise: false */
|
||||||
/* global µBlock */
|
/* global µBlock */
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -48,21 +49,24 @@ var netFilteringResultCacheEntryJunkyardMax = 200;
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var NetFilteringResultCacheEntry = function(data) {
|
var NetFilteringResultCacheEntry = function(result, type, flags) {
|
||||||
this.init(data);
|
this.init(result, type, flags);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
NetFilteringResultCacheEntry.prototype.init = function(data) {
|
NetFilteringResultCacheEntry.prototype.init = function(result, type, flags) {
|
||||||
this.data = data;
|
this.result = result;
|
||||||
|
this.type = type;
|
||||||
|
this.flags = flags;
|
||||||
this.time = Date.now();
|
this.time = Date.now();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
NetFilteringResultCacheEntry.prototype.dispose = function() {
|
NetFilteringResultCacheEntry.prototype.dispose = function() {
|
||||||
this.data = null;
|
this.result = '';
|
||||||
|
this.type = '';
|
||||||
if ( netFilteringResultCacheEntryJunkyard.length < netFilteringResultCacheEntryJunkyardMax ) {
|
if ( netFilteringResultCacheEntryJunkyard.length < netFilteringResultCacheEntryJunkyardMax ) {
|
||||||
netFilteringResultCacheEntryJunkyard.push(this);
|
netFilteringResultCacheEntryJunkyard.push(this);
|
||||||
}
|
}
|
||||||
@ -70,12 +74,12 @@ NetFilteringResultCacheEntry.prototype.dispose = function() {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
NetFilteringResultCacheEntry.factory = function(data) {
|
NetFilteringResultCacheEntry.factory = function(result, type, flags) {
|
||||||
var entry = netFilteringResultCacheEntryJunkyard.pop();
|
var entry = netFilteringResultCacheEntryJunkyard.pop();
|
||||||
if ( entry === undefined ) {
|
if ( entry === undefined ) {
|
||||||
entry = new NetFilteringResultCacheEntry(data);
|
entry = new NetFilteringResultCacheEntry(result, type, flags);
|
||||||
} else {
|
} else {
|
||||||
entry.init(data);
|
entry.init(result, type, flags);
|
||||||
}
|
}
|
||||||
return entry;
|
return entry;
|
||||||
};
|
};
|
||||||
@ -136,14 +140,16 @@ NetFilteringResultCache.prototype.dispose = function() {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
NetFilteringResultCache.prototype.add = function(url, data) {
|
NetFilteringResultCache.prototype.add = function(url, result, type, flags) {
|
||||||
var entry = this.urls[url];
|
var entry = this.urls[url];
|
||||||
if ( entry !== undefined ) {
|
if ( entry !== undefined ) {
|
||||||
entry.data = data;
|
entry.result = result;
|
||||||
|
entry.type = type;
|
||||||
|
entry.flags = flags;
|
||||||
entry.time = Date.now();
|
entry.time = Date.now();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.urls[url] = NetFilteringResultCacheEntry.factory(data);
|
this.urls[url] = NetFilteringResultCacheEntry.factory(result, type, flags);
|
||||||
if ( this.count === 0 ) {
|
if ( this.count === 0 ) {
|
||||||
this.pruneAsync();
|
this.pruneAsync();
|
||||||
}
|
}
|
||||||
@ -184,7 +190,7 @@ NetFilteringResultCache.prototype.prune = function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://www.youtube.com/watch?v=0vTBZzB_gfY
|
// https://www.youtube.com/watch?v=hcVpbsDyOhM
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -201,8 +207,7 @@ NetFilteringResultCache.prototype.pruneAsync = function() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
NetFilteringResultCache.prototype.lookup = function(url) {
|
NetFilteringResultCache.prototype.lookup = function(url) {
|
||||||
var entry = this.urls[url];
|
return this.urls[url];
|
||||||
return entry !== undefined ? entry.data : undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -308,7 +313,20 @@ PageStore.prototype.init = function(tabId, pageURL) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
PageStore.prototype.reuse = function(pageURL) {
|
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.disposeFrameStores();
|
||||||
this.netFilteringCache = this.netFilteringCache.dispose();
|
this.netFilteringCache = this.netFilteringCache.dispose();
|
||||||
var previousPageURL = this.pageURL;
|
var previousPageURL = this.pageURL;
|
||||||
@ -383,21 +401,30 @@ PageStore.prototype.getNetFilteringSwitch = function() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
PageStore.prototype.filterRequest = function(context, requestType, requestURL) {
|
PageStore.prototype.filterRequest = function(context, requestType, requestURL) {
|
||||||
var result = this.netFilteringCache.lookup(requestURL);
|
var entry = this.netFilteringCache.lookup(requestURL);
|
||||||
if ( result !== undefined ) {
|
if ( entry !== undefined ) {
|
||||||
//console.debug(' cache HIT: PageStore.filterRequest("%s")', requestURL);
|
//console.debug(' cache HIT: PageStore.filterRequest("%s")', requestURL);
|
||||||
return result.slice(result.indexOf('\t') + 1);
|
return entry.result;
|
||||||
}
|
}
|
||||||
//console.debug('cache MISS: PageStore.filterRequest("%s")', requestURL);
|
//console.debug('cache MISS: PageStore.filterRequest("%s")', requestURL);
|
||||||
result = µb.netFilteringEngine.matchString(context, requestURL, requestType);
|
var result = µb.netFilteringEngine.matchString(context, requestURL, requestType);
|
||||||
if ( collapsibleRequestTypes.indexOf(requestType) !== -1 || µb.userSettings.logRequests ) {
|
if ( collapsibleRequestTypes.indexOf(requestType) !== -1 || µb.userSettings.logRequests ) {
|
||||||
this.netFilteringCache.add(requestURL, requestType + '\t' + result);
|
this.netFilteringCache.add(requestURL, result, requestType, 0);
|
||||||
}
|
}
|
||||||
return result;
|
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
|
// false: not blocked
|
||||||
// true: blocked
|
// true: blocked
|
||||||
|
|
||||||
|
24
js/stats.js
24
js/stats.js
@ -19,6 +19,7 @@
|
|||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* jshint bitwise: false */
|
||||||
/* global chrome, uDom, messaging */
|
/* global chrome, uDom, messaging */
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -55,6 +56,18 @@ var toPrettyTypeNames = {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var chunkify = function(s) {
|
||||||
|
var chunkSize = 50;
|
||||||
|
var chunks = [];
|
||||||
|
while ( s.length ) {
|
||||||
|
chunks.push(s.slice(0, chunkSize));
|
||||||
|
s = s.slice(chunkSize);
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
var renderURL = function(url, filter) {
|
var renderURL = function(url, filter) {
|
||||||
var chunkSize = 50;
|
var chunkSize = 50;
|
||||||
// make a regex out of the filter
|
// make a regex out of the filter
|
||||||
@ -75,12 +88,7 @@ var renderURL = function(url, filter) {
|
|||||||
;
|
;
|
||||||
var re = new RegExp(reText, 'gi');
|
var re = new RegExp(reText, 'gi');
|
||||||
var matches = re.exec(url);
|
var matches = re.exec(url);
|
||||||
|
var renderedURL = chunkify(url);
|
||||||
var renderedURL = [];
|
|
||||||
while ( url.length ) {
|
|
||||||
renderedURL.push(url.slice(0, chunkSize));
|
|
||||||
url = url.slice(chunkSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( matches && matches[0].length ) {
|
if ( matches && matches[0].length ) {
|
||||||
var index = (re.lastIndex / chunkSize) | 0;
|
var index = (re.lastIndex / chunkSize) | 0;
|
||||||
@ -145,11 +153,11 @@ var renderPageDetails = function(tabId) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
html.push(
|
html.push(
|
||||||
'<tr class="', className, ' requestEntry">',
|
'<tr class="', className, request.flags & 0x01 ? ' logMirrored': '', ' requestEntry">',
|
||||||
'<td>',
|
'<td>',
|
||||||
'<td>', toPrettyTypeNames[request.type] || request.type,
|
'<td>', toPrettyTypeNames[request.type] || request.type,
|
||||||
'<td>', renderURL(request.url, request.reason),
|
'<td>', renderURL(request.url, request.reason),
|
||||||
'<td>', request.reason || ''
|
'<td>', chunkify(request.reason).join('\n')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return html;
|
return html;
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
if ( !changeInfo.url ) {
|
if ( !changeInfo.url ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
µb.bindTabToPageStats(tabId, changeInfo.url);
|
µb.bindTabToPageStats(tabId, changeInfo.url, 'tabUpdated');
|
||||||
}
|
}
|
||||||
chrome.tabs.onUpdated.addListener(onTabUpdated);
|
chrome.tabs.onUpdated.addListener(onTabUpdated);
|
||||||
|
|
||||||
@ -103,7 +103,7 @@
|
|||||||
|
|
||||||
if ( pageStore ) {
|
if ( pageStore ) {
|
||||||
if ( pageURL !== pageStore.pageURL || context === 'beforeRequest' ) {
|
if ( pageURL !== pageStore.pageURL || context === 'beforeRequest' ) {
|
||||||
pageStore.reuse(pageURL);
|
pageStore.reuse(pageURL, context);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pageStore = this.pageStores[tabId] = this.PageStore.factory(tabId, pageURL);
|
pageStore = this.pageStores[tabId] = this.PageStore.factory(tabId, pageURL);
|
||||||
|
@ -40,20 +40,9 @@ var onBeforeRequest = function(details) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/206
|
|
||||||
// https://code.google.com/p/chromium/issues/detail?id=410382
|
|
||||||
// Work around the issue of Chromium not properly setting the type for
|
|
||||||
// `object` requests. Unclear whether this issue will be fixed, hence this
|
|
||||||
// workaround to prevent torch-and-pitchfork mobs because ads are no longer
|
|
||||||
// blocked in videos.
|
|
||||||
// onBeforeSendHeaders() will handle this for now.
|
|
||||||
var requestType = details.type;
|
|
||||||
if ( requestType === 'other' ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var µb = µBlock;
|
var µb = µBlock;
|
||||||
var requestURL = details.url;
|
var requestURL = details.url;
|
||||||
|
var requestType = details.type;
|
||||||
|
|
||||||
// Special handling for root document.
|
// Special handling for root document.
|
||||||
if ( requestType === 'main_frame' && details.parentFrameId === -1 ) {
|
if ( requestType === 'main_frame' && details.parentFrameId === -1 ) {
|
||||||
@ -64,13 +53,20 @@ var onBeforeRequest = function(details) {
|
|||||||
// Commented out until (and if ever) there is a fix for:
|
// Commented out until (and if ever) there is a fix for:
|
||||||
// https://code.google.com/p/chromium/issues/detail?id=410382
|
// https://code.google.com/p/chromium/issues/detail?id=410382
|
||||||
//
|
//
|
||||||
// Try to transpose generic `other` category into something more
|
// Try to transpose generic `other` category into something more meaningful.
|
||||||
// meaningful.
|
if ( requestType === 'other' ) {
|
||||||
//var µburi = µb.URI.set(requestURL);
|
requestType = µb.transposeType('other', µb.URI.set(requestURL).path);
|
||||||
//var requestPath = µburi.path;
|
// https://github.com/gorhill/uBlock/issues/206
|
||||||
//if ( requestType === 'other' ) {
|
// https://code.google.com/p/chromium/issues/detail?id=410382
|
||||||
// requestType = µb.transposeType(requestType, requestPath);
|
// Work around the issue of Chromium not properly setting the type for
|
||||||
//}
|
// `object` requests. Unclear whether this issue will be fixed, hence
|
||||||
|
// this workaround to prevent torch-and-pitchfork mobs because ads are
|
||||||
|
// no longer blocked in videos.
|
||||||
|
// onBeforeSendHeaders() will handle this for now.
|
||||||
|
if ( requestType === 'other' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Lookup the page store associated with this tab id.
|
// Lookup the page store associated with this tab id.
|
||||||
var pageStore = µb.pageStoreFromTabId(tabId);
|
var pageStore = µb.pageStoreFromTabId(tabId);
|
||||||
@ -105,9 +101,12 @@ var onBeforeRequest = function(details) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( µb.userSettings.experimentalEnabled ) {
|
if ( µb.userSettings.experimentalEnabled ) {
|
||||||
|
// https://code.google.com/p/chromium/issues/detail?id=387198
|
||||||
|
// Not all redirects will succeed, until bug above is fixed.
|
||||||
var redirectURL = µb.mirrors.toURL(requestURL, true);
|
var redirectURL = µb.mirrors.toURL(requestURL, true);
|
||||||
if ( redirectURL !== '' ) {
|
if ( redirectURL !== '' ) {
|
||||||
//console.debug('"%s" redirected to "%s..."', requestURL.slice(0, 50), redirectURL.slice(0, 50));
|
pageStore.setRequestFlags(requestURL, 0x01, 0x01);
|
||||||
|
console.debug('"%s" redirected to "%s..."', requestURL.slice(0, 50), redirectURL.slice(0, 50));
|
||||||
return { redirectUrl: redirectURL };
|
return { redirectUrl: redirectURL };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,19 +226,22 @@ var cr410382Workaround = function(details) {
|
|||||||
var µb = µBlock;
|
var µb = µBlock;
|
||||||
var requestURL = details.url;
|
var requestURL = details.url;
|
||||||
var µburi = µb.URI.set(requestURL);
|
var µburi = µb.URI.set(requestURL);
|
||||||
var requestPath = µburi.path;
|
|
||||||
|
// If the type can be successfully transposed, this means the request
|
||||||
|
// was processed at onBeforeRequest time.
|
||||||
|
if ( µb.transposeType('other', µburi.path) !== 'other' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Lookup "X-Requested-With" header: this will tell us whether the request
|
// Lookup "X-Requested-With" header: this will tell us whether the request
|
||||||
// is of type "object".
|
// is of type "object".
|
||||||
// Reference: https://code.google.com/p/chromium/issues/detail?id=145090
|
// Reference: https://code.google.com/p/chromium/issues/detail?id=145090
|
||||||
var requestedWith = headerValue(details.requestHeaders, 'x-requested-with');
|
var requestedWith = headerValue(details.requestHeaders, 'x-requested-with');
|
||||||
|
|
||||||
// Try to transpose generic `other` category into something more
|
|
||||||
// meaningful.
|
|
||||||
// Reference: https://codereview.chromium.org/451923002/patch/120001/130008
|
// Reference: https://codereview.chromium.org/451923002/patch/120001/130008
|
||||||
var requestType = requestedWith.indexOf('ShockwaveFlash') !== -1 ?
|
var requestType = requestedWith.indexOf('ShockwaveFlash') !== -1 ?
|
||||||
'object' :
|
'object' :
|
||||||
µb.transposeType(details.type, requestPath);
|
'other';
|
||||||
|
|
||||||
// Lookup the page store associated with this tab id.
|
// Lookup the page store associated with this tab id.
|
||||||
var pageStore = µb.pageStoreFromTabId(details.tabId);
|
var pageStore = µb.pageStoreFromTabId(details.tabId);
|
||||||
@ -364,8 +366,8 @@ chrome.webRequest.onBeforeRequest.addListener(
|
|||||||
"script",
|
"script",
|
||||||
"image",
|
"image",
|
||||||
"object",
|
"object",
|
||||||
"xmlhttprequest"
|
"xmlhttprequest",
|
||||||
// "other" // Because cr410382Workaround()
|
"other"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[ "blocking" ]
|
[ "blocking" ]
|
||||||
|
@ -238,7 +238,7 @@
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
var ext = path.slice(pos) + '.';
|
var ext = path.slice(pos) + '.';
|
||||||
if ( '.eot.ttf.otf.svg.woff.'.indexOf(ext) !== -1 ) {
|
if ( '.eot.ttf.otf.svg.woff.woff2.'.indexOf(ext) !== -1 ) {
|
||||||
return 'stylesheet';
|
return 'stylesheet';
|
||||||
}
|
}
|
||||||
if ( '.ico.png.gif.jpg.jpeg.'.indexOf(ext) !== -1 ) {
|
if ( '.ico.png.gif.jpg.jpeg.'.indexOf(ext) !== -1 ) {
|
||||||
|
@ -79,13 +79,16 @@ tr.logBlocked ~ tr td:nth-of-type(3) b {
|
|||||||
background-color: rgba(255,0,0,0.1);
|
background-color: rgba(255,0,0,0.1);
|
||||||
}
|
}
|
||||||
tr.logAllowed {
|
tr.logAllowed {
|
||||||
background-color: #f8fff8 !important;
|
background-color: #f8fff8
|
||||||
}
|
}
|
||||||
tr.logAllowed ~ tr td:nth-of-type(3) b {
|
tr.logAllowed ~ tr td:nth-of-type(3) b {
|
||||||
padding: 2px 0;
|
padding: 2px 0;
|
||||||
color: #000;
|
color: #000;
|
||||||
background-color: rgba(0,255,0,0.2);
|
background-color: rgba(0,255,0,0.2);
|
||||||
}
|
}
|
||||||
|
tr.logMirrored {
|
||||||
|
background-color: #ffffbb !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user