lot of work related to dynamic filtering + new net requests logger

This commit is contained in:
gorhill 2015-01-06 08:01:15 -05:00
parent 81e035589b
commit 1597ce7fd9
23 changed files with 991 additions and 718 deletions

View file

@ -208,6 +208,15 @@ vAPI.tabs.remove = function(tabId) {
/******************************************************************************/
vAPI.tabs.reload = function(tabId, flags) {
if ( typeof tabId === 'string' ) {
tabId = parseInt(tabId, 10);
}
chrome.tabs.reload(tabId);
};
/******************************************************************************/
vAPI.tabs.injectScript = function(tabId, details, callback) {
var onScriptExecuted = function() {
// https://code.google.com/p/chromium/issues/detail?id=410868#c8

68
src/css/devtool-log.css Normal file
View file

@ -0,0 +1,68 @@
body {
border: 0;
box-sizing: border-box;
font: 11px monospace;
margin: 0;
overflow-x: hidden;
padding: 0;
white-space: nowrap;
width: 100%;
}
#toolbar {
padding: 8px 0;
position: fixed;
text-align: center;
top: 0;
width: 4em;
}
#toolbar .button {
background-color: white;
border: none;
cursor: pointer;
display: block;
font-size: large;
margin: 0;
padding: 0.5em 0;
}
#toolbar .button:hover {
background-color: #eee;
}
#content {
margin-left: 4em;
width: calc(100% - 4em);
}
#content table {
border: 0;
border-collapse: collapse;
width: 100%;
}
#content table tr.blocked {
background-color: rgba(192, 0, 0, 0.1)
}
#content table tr.allowed {
background-color: rgba(0, 160, 0, 0.1)
}
#content table tr td {
border: 1px solid #ccc;
hyphens: none;
padding: 3px;
vertical-align: top;
white-space: normal;
word-break: break-all;
word-wrap: break-word;
}
#content table tr td:nth-of-type(1) {
width: 15%;
}
#content table tr td:nth-of-type(3) {
border-right: none;
width: 75%;
}
#content table tr.blocked td:nth-of-type(3) b {
background-color: rgba(192, 0, 0, 0.2);
font-weight: normal;
}
#content table tr.allowed td:nth-of-type(3) b {
background-color: rgba(0, 160, 0, 0.2);
font-weight: normal;
}

49
src/css/devtools.css Normal file
View file

@ -0,0 +1,49 @@
body {
font-size: 13px;
margin: 0;
overflow-y: hidden;
padding: 0;
}
#toolbar {
background-color: #eee;
border: none;
box-sizing: border-box;
height: 4em;
padding: 1em;
position: fixed;
top: 0;
width: 100%;
}
#toolbar > * {
display: inline-block;
vertical-align: middle;
}
#toolbar button {
background-color: transparent;
border: none;
cursor: pointer;
font-size: 2em;
margin: 0 0 0 1em;
vertical-align: middle;
}
#toolbar #refresh {
margin-left: 4px;
}
select {
padding: 2px 0;
font-size: 14px;
min-width: 20em;
max-width: 40em;
}
select option {
max-width: 40em;
}
#content {
border: 0;
box-sizing: border-box;
height: calc(100vh - 4em);
margin-top: 4em;
overflow-y: auto;
padding: 0;
width: 100%;
}

View file

@ -28,22 +28,41 @@ a {
font-weight: normal;
margin-left: 1em;
}
body > div {
background-color: transparent;
body[dir="ltr"] #panes {
direction: rtl;
}
body[dir="rtl"] #panes {
direction: ltr;
}
#panes > div {
display: inline-block;
position: relative;
vertical-align: top;
}
body > div:nth-of-type(1) {
direction: rtl; /* scroll bar to the left */
body[dir="ltr"] #panes > div {
direction: ltr;
}
body[dir="rtl"] #panes > div {
direction: rtl;
}
#panes > div:nth-of-type(2) {
overflow-y: hidden;
overflow-x: hidden;
width: 0;
}
body.dynamicFilteringEnabled > div:nth-of-type(1) {
body[dir="ltr"] #panes > div:nth-of-type(2) {
direction: rtl; /* scroll bar to the left */
}
body[dir="rtl"] #panes > div:nth-of-type(2) {
direction: ltr; /* scroll bar to the right */
}
#panes.dfEnabled > div:nth-of-type(2) {
overflow-y: auto;
width: 320px;
}
body > div:nth-of-type(2) {
padding: 4px 12px 0 5px;
#panes > div:nth-of-type(1) {
padding: 4px 5px 0 5px;
}
p {
margin: 16px 0;
@ -96,17 +115,14 @@ p {
color: #444;
}
#dynamicFilteringToggler {
pointer-events: none;
}
#dynamicFilteringToggler::before {
#dfToggler::before {
color: gray;
content: '+\202F';
cursor: pointer;
font-size: 13px;
pointer-events: auto;
font-size: 14px;
line-height: 14px;
}
body.dynamicFilteringEnabled #dynamicFilteringToggler::before {
#panes.dfEnabled #dfToggler::before {
content: '\2212\202F';
}
@ -142,24 +158,16 @@ body.dynamicFilteringEnabled #dynamicFilteringToggler::before {
margin: 0;
padding: 0;
text-align: right;
width: 5px;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer {
display: block;
width: auto;
}
#dynamicFilteringContainer > div {
background-color: transparent;
background-color: #e6e6e6;
border: 0;
border-bottom: 1px solid white;
direction: ltr;
margin: 0;
padding: 0;
width: 320px;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer > div {
background-color: #e6e6e6;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer > div:hover {
#dynamicFilteringContainer > div:hover {
background-color: #f0f0f0;
}
#dynamicFilteringContainer > div#privacyInfo {
@ -168,30 +176,21 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div:hover {
padding: 4px 0;
text-align: center;
}
#dynamicFilteringContainer > div.isDomain {
margin-top: 2px;
}
#dynamicFilteringContainer > div > span {
background-color: transparent;
border: none;
border-bottom: 1px solid white;
box-sizing: border-box;
color: transparent;
color: #000;
display: inline-block;
height: 24px;
line-height: 24px;
pointer-events: none;
overflow: hidden;
position: relative;
vertical-align: top;
}
body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
color: #000;
overflow: hidden;
pointer-events: auto;
}
#dynamicFilteringContainer > div > span:nth-of-type(1) {
border-right: 1px solid white;
padding-right: 4px;
padding-right: 2px;
text-overflow: ellipsis;
width: 70%;
}
@ -201,6 +200,7 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
}
#dynamicFilteringContainer > div > span:nth-of-type(3) {
border-left: 1px solid white;
color: #444;
cursor: pointer;
text-align: center;
width: 15%;
@ -208,9 +208,14 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
#dynamicFilteringContainer > div.isDomain > span:nth-of-type(1) {
font-weight: bold;
}
#dynamicFilteringContainer > div > span:nth-of-type(3) {
color: #666;
pointer-events: auto;
#dynamicFilteringContainer > div.allowed > span:nth-of-type(1) {
background-color: rgba(0, 160, 0, 0.1);
}
#dynamicFilteringContainer > div.blocked > span:nth-of-type(1) {
background-color: rgba(192, 0, 0, 0.1);
}
#dynamicFilteringContainer > div.allowed.blocked > span:nth-of-type(1) {
background-color: rgba(192, 160, 0, 0.1);
}
#dynamicFilteringContainer > div > span.aRule {
background-color: rgba(0, 160, 0, 0.3);
@ -221,17 +226,17 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
#dynamicFilteringContainer > div > span.nRule {
background-color: rgba(96, 96, 96, 0.3);
}
#dynamicFilteringContainer > div > span.ownRule {
color: white;
}
#dynamicFilteringContainer > div > span.aRule.ownRule {
background-color: rgba(0, 160, 0, 1);
color: white;
}
#dynamicFilteringContainer > div > span.bRule.ownRule {
background-color: rgba(192, 0, 0, 1);
color: white;
}
#dynamicFilteringContainer > div > span.nRule.ownRule {
background-color: rgba(108, 108, 108, 1);
color: white;
}
#actionSelector {
@ -264,3 +269,17 @@ body.dynamicFilteringEnabled #dynamicFilteringContainer > div > span {
#dynamicFilteringContainer span.bRule #actionSelector > span:nth-of-type(3) {
visibility: hidden;
}
#hotspotTip {
background-color: #ffe;
border: 1px dotted #ddb;
border-radius: 5px;
height: 50vh;
opacity: 1;
padding: 4px;
position: fixed;
right: 10px;
text-align: center;
top: 25vh;
width: 20vw;
z-index: 100;
}

View file

@ -1,86 +0,0 @@
div {
margin: 1em 0;
}
ul {
list-style-type: none;
}
#refresh {
margin: 0 0.5em 0 4px;
display: inline-block;
vertical-align: middle;
font-size: 2em;
cursor: pointer;
}
select {
padding: 2px 0;
font-size: 14px;
min-width: 20em;
max-width: 40em;
}
select option {
max-width: 40em;
}
#requests {
margin: 2em 0 0 0;
display: none;
}
#requests.logEnabled {
display: block;
}
#requests table {
margin: 1em 0;
border: 0;
border-collapse: collapse;
min-width: 600px;
}
#requests.empty table {
display: none;
}
tr td, tr th {
border: 1px solid #aaa;
padding: 4px 6px;
white-space: pre;
}
tr.domainHeader td {
font: 16px sans-serif;
}
tr.domainHeader td span {
margin-right: 0.5em;
font-size: 14px;
color: #aaa;
cursor: pointer;
}
tr.requestEntry {
font: 12px monospace;
}
tr.requestEntry td:nth-of-type(1) {
border: 0;
background-color: white;
width: 3em;
}
tr.requestEntry td:nth-of-type(2) {
text-align: right;
}
tr.requestEntry td:nth-of-type(3),
tr.requestEntry td:nth-of-type(4) {
direction: ltr;
}
tr.logBlocked {
background-color: #fff8f8;
}
tr.logBlocked ~ tr td:nth-of-type(3) b {
padding: 2px 0;
color: #000;
background-color: rgba(255,0,0,0.1);
}
tr.logAllowed {
background-color: #f8fff8
}
tr.logAllowed ~ tr td:nth-of-type(3) b {
padding: 2px 0;
color: #000;
background-color: rgba(0,255,0,0.2);
}
tr.logMirrored {
background-color: #ffffbb !important;
}

View file

@ -16,7 +16,6 @@
<a class="tabButton" href="#dyna-rules.html" data-i18n="rulesPageName"></a>
<a class="tabButton" href="#whitelist.html" data-i18n="whitelistPageName"></a>
<a class="tabButton" href="#settings.html" data-i18n="settingsPageName"></a>
<a class="tabButton" href="#stats.html" data-i18n="statsPageName"></a>
<a class="tabButton" href="#about.html" data-i18n="aboutPageName"></a>
</div>
</div>

20
src/devtool-log.html Normal file
View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/devtool-log.css">
<title>µBlock log</title>
</head>
<body>
<div id="toolbar">
<span id="reload" class="button fa">&#xf021;</span>
<span id="clear" class="button fa">&#xf12d;</span>
</div><!-- DO NOT REMOVE --><div id="content">
<table><tbody></tbody></table>
</div>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/devtool-log.js"></script>
</body>
</html>

25
src/devtools.html Normal file
View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>µBlock — Statistics</title>
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/devtools.css">
</head>
<body>
<div id="toolbar">
<select id="pageSelector"></select>
<button id="refresh" class="fa" type="button">&#xf021;</button>
</div>
<iframe id="content"></iframe>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/devtools.js"></script>
</body>
</html>

View file

@ -60,7 +60,6 @@ return {
dynamicFilteringEnabled: false,
experimentalEnabled: false,
externalLists: defaultExternalLists,
logRequests: false,
parseAllABPHideFilters: true,
showIconBadge: true
},

View file

@ -997,9 +997,15 @@ FilterContainer.prototype.removeFromSelectorCache = function(targetHostname, typ
if ( this.selectorCache.hasOwnProperty(hostname) === false ) {
continue;
}
if ( targetHostname !== '*' && hostname !== targetHostname ) {
if ( targetHostname !== '*' ) {
if ( hostname.slice(0 - targetHostname.length) !== targetHostname ) {
continue;
}
if ( hostname.length !== targetHostname.length &&
hostname.charAt(0 - targetHostname.length - 1) !== '.' ) {
continue;
}
}
this.selectorCache[hostname].remove(type);
}
};

178
src/js/devtool-log.js Normal file
View file

@ -0,0 +1,178 @@
/*******************************************************************************
sessbench - a Chromium browser extension to benchmark browser session.
Copyright (C) 2013 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/sessbench
TODO: cleanup/refactor
*/
/* global vAPI, uDom */
/******************************************************************************/
(function() {
/******************************************************************************/
var messager = vAPI.messaging.channel('devtool-log.js');
var inspectedTabId = '';
var doc = document;
var body = doc.body;
var tbody = doc.querySelector('#content tbody');
var rowJunkyard = [];
/******************************************************************************/
var renderURL = function(url, filter) {
if ( filter.charAt(0) !== 's' ) {
return url;
}
// make a regex out of the filter
var reText = filter.slice(3);
var pos = reText.indexOf('$');
if ( pos > 0 ) {
reText = reText.slice(0, pos);
}
if ( reText === '*' ) {
reText = '\\*';
} else {
reText = reText
.replace(/\./g, '\\.')
.replace(/\?/g, '\\?')
.replace('||', '')
.replace(/\^/g, '.')
.replace(/\*/g, '.*')
;
}
var re = new RegExp(reText, 'gi');
var matches = re.exec(url);
var renderedURL = url;
if ( matches && matches[0].length ) {
renderedURL = url.slice(0, matches.index) +
'<b>' +
url.slice(matches.index, re.lastIndex) +
'</b>' +
url.slice(re.lastIndex);
}
return renderedURL;
};
/******************************************************************************/
var createRow = function() {
var tr = rowJunkyard.pop();
if ( tr ) {
tr.className = '';
return tr;
}
tr = doc.createElement('tr');
tr.appendChild(doc.createElement('td'));
tr.appendChild(doc.createElement('td'));
tr.appendChild(doc.createElement('td'));
return tr;
};
/******************************************************************************/
var renderLogEntry = function(entry) {
var tr = createRow();
if ( entry.result.charAt(1) === 'b' ) {
tr.classList.add('blocked');
} else if ( entry.result.charAt(1) === 'a' ) {
tr.classList.add('allowed');
}
tr.cells[0].textContent = entry.result.slice(3);
tr.cells[1].textContent = entry.type;
tr.cells[2].innerHTML = renderURL(entry.url, entry.result);
tbody.insertBefore(tr, tbody.firstChild);
};
/******************************************************************************/
var renderLogBuffer = function(buffer) {
// Preserve scroll position
var height = tbody.offsetHeight;
var n = buffer.length;
for ( var i = 0; i < n; i++ ) {
renderLogEntry(buffer[i]);
}
if ( body.scrollTop !== 0 ) {
body.scrollTop += tbody.offsetHeight - height;
}
};
/******************************************************************************/
var onBufferRead = function(buffer) {
if ( Array.isArray(buffer ) ) {
renderLogBuffer(buffer);
}
setTimeout(readLogBuffer, 1000);
};
/******************************************************************************/
// This can be called only once, at init time. After that, this will be called
// automatically. If called after init time, this will be messy, and this would
// require a bit more code to ensure no multi time out events.
var readLogBuffer = function() {
messager.send({ what: 'readLogBuffer', tabId: inspectedTabId }, onBufferRead);
};
/******************************************************************************/
var clearBuffer = function() {
var rows = tbody.rows;
var row;
var i = rows.length;
while ( i-- ) {
row = rows[i];
row.parentNode.removeChild(row);
rowJunkyard.push(row);
}
};
/******************************************************************************/
var reloadTab = function() {
messager.send({ what: 'reloadTab', tabId: inspectedTabId });
};
/******************************************************************************/
uDom.onLoad(function() {
// Extract the tab id of the page we need to pull the log
var matches = window.location.search.match(/[\?&]tabId=([^&]+)/);
if ( matches && matches.length === 2 ) {
inspectedTabId = matches[1];
}
readLogBuffer();
uDom('#reload').on('click', reloadTab);
uDom('#clear').on('click', clearBuffer);
});
/******************************************************************************/
})();

98
src/js/devtools.js Normal file
View file

@ -0,0 +1,98 @@
/*******************************************************************************
µ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 vAPI, uDom */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
var messager = vAPI.messaging.channel('stats.js');
/******************************************************************************/
var renderPageSelector = function(targetTabId) {
var selectedTabId = targetTabId || uDom('#pageSelector').val();
var onTabReceived = function(tabId, tabTitle) {
uDom('#pageSelector').append('<option value="' + tabId + '">' + tabTitle);
if ( tabId.toString() === selectedTabId ) {
uDom('#pageSelector').val(tabId);
}
};
var onDataReceived = function(pageDetails) {
uDom('#pageSelector option').remove();
if ( pageDetails.hasOwnProperty(selectedTabId) === false ) {
selectedTabId = pageDetails[0];
}
for ( var tabId in pageDetails ) {
if ( pageDetails.hasOwnProperty(tabId) ) {
onTabReceived(tabId, pageDetails[tabId]);
}
}
selectPage();
};
messager.send({ what: 'getPageDetails' }, onDataReceived);
};
/******************************************************************************/
var pageSelectorChanged = function() {
selectPage();
};
/******************************************************************************/
var selectPage = function() {
var tabId = uDom('#pageSelector').val() || '';
var inspector = uDom('#content');
var currentSrc = inspector.attr('src');
var targetSrc = 'devtool-log.html?tabId=' + tabId;
if ( targetSrc !== currentSrc ) {
inspector.attr('src', targetSrc);
}
};
/******************************************************************************/
uDom.onLoad(function() {
var tabId;
// Extract the tab id of the page we need to pull the log
var matches = window.location.search.match(/[\?&]tabId=([^&]+)/);
if ( matches && matches.length === 2 ) {
tabId = matches[1];
}
renderPageSelector(tabId);
uDom('#pageSelector').on('change', pageSelectorChanged);
uDom('#refresh').on('click', function() { renderPageSelector(); } );
});
/******************************************************************************/
})();

View file

@ -38,13 +38,25 @@ var Matrix = function() {
/******************************************************************************/
var supportedTypes = {
'*': true,
'inline-script': true,
'script': true,
'1p-script': true,
'3p-script': true,
'sub_frame': true,
'3p-frame': true,
'image': true
};
var typeBitOffsets = {
'*': 0,
'inline-script': 2,
'1p-script': 4,
'3p-script': 6,
'3p-frame': 8,
'image': 10
'image': 10,
'3p-any': 12
};
var actionToNameMap = {
@ -192,6 +204,19 @@ Matrix.prototype.clearRegisters = function() {
/******************************************************************************/
var isFirstParty = function(srcHostname, desHostname) {
if ( desHostname.slice(0 - srcHostname.length) !== srcHostname ) {
return false;
}
// Be sure to not confuse 'example.com' with 'anotherexample.com'
if ( desHostname.lenght === srcHostname.lenght ) {
return true;
}
return desHostname.charAt(desHostname.length - srcHostname.length - 1) === '.';
};
/******************************************************************************/
Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
var bitOffset = typeBitOffsets[type];
var s = srcHostname;
@ -217,32 +242,42 @@ Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
/******************************************************************************/
Matrix.prototype.evaluateCellZY = function(srcHostname, desHostname, type) {
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
this.type = '';
this.r = 0;
if ( supportedTypes.hasOwnProperty(type) === false ) {
this.type = '';
return this;
}
this.type = type;
// Specific-hostname specific-type cell
this.type = '*';
// Specific-destination + any type
this.y = desHostname;
this.r = this.evaluateCellZ(srcHostname, desHostname, type);
if ( this.r !== 0 ) { return this; }
var d = desHostname;
for (;;) {
d = toBroaderHostname(d);
if ( d === '*' ) {
break;
}
// specific-hostname specific-type cell
this.y = d;
this.r = this.evaluateCellZ(srcHostname, d, type);
if ( this.r !== 0 ) { return this; }
}
// Any-hostname specific-type cells
// Any destination + specific-type
this.y = '*';
if ( type === 'script' ) {
type = isFirstParty(srcHostname, desHostname) ? '1p-script' : '3p-script';
} else if ( type === 'sub-frame' && isFirstParty(srcHostname, desHostname) === false ) {
type = '3p-frame';
}
this.type = type;
this.r = this.evaluateCellZ(srcHostname, '*', type);
return this;
};
@ -447,7 +482,7 @@ Matrix.prototype.fromSelfie = function(selfie) {
/******************************************************************************/
return new Matrix;
return new Matrix();
/******************************************************************************/

View file

@ -71,6 +71,10 @@ var onMessage = function(request, sender, callback) {
µb.reloadPresetBlacklists(request.switches, request.update);
break;
case 'reloadTab':
vAPI.tabs.reload(request.tabId);
break;
case 'userSettings':
response = µb.changeUserSettings(request.name, request.value);
break;
@ -177,8 +181,7 @@ var getStats = function(tabId) {
pageAllowedRequestCount: 0,
netFilteringSwitch: false,
cosmeticFilteringSwitch: false,
logRequests: µb.userSettings.logRequests,
dynamicFilteringEnabled: µb.userSettings.dynamicFilteringEnabled
dfEnabled: µb.userSettings.dynamicFilteringEnabled
};
var pageStore = µb.pageStoreFromTabId(tabId);
if ( pageStore ) {
@ -749,50 +752,30 @@ var µb = µBlock;
/******************************************************************************/
var getPageDetails = function(µb, tabId) {
var r = {
blockedRequests: [],
allowedRequests: [],
hash: ''
var getPageDetails = function(callback) {
var out = {};
var tabIds = Object.keys(µb.pageStores);
var countdown = tabIds.length;
if ( countdown === 0 ) {
callback(out);
return;
}
var onTabDetails = function(tab) {
if ( tab ) {
out[tab.id] = tab.title;
}
countdown -= 1;
if ( countdown === 0 ) {
callback(out);
}
};
var pageStore = µb.pageStores[tabId];
if ( !pageStore ) {
return r;
var i = countdown;
while ( i-- ) {
vAPI.tabs.get(tabIds[i], onTabDetails);
}
var prepareRequests = function(wantBlocked, hasher) {
var µburi = µb.URI;
var dict = pageStore.netFilteringCache.fetchAll();
var r = [];
var details, hostname, domain;
for ( var url in dict ) {
if ( dict.hasOwnProperty(url) === false ) {
continue;
}
details = dict[url];
if ( wantBlocked !== pageStore.boolFromResult(details.result) ) {
continue;
}
hasher.appendStr(url);
hasher.appendStr(details.result);
hostname = µburi.hostnameFromURI(url);
domain = µburi.domainFromHostname(hostname) || hostname;
r.push({
url: url,
domain: domain,
reason: details.result,
type: details.type,
flags: details.flags
});
}
return r;
};
var hasher = new YaMD5();
if ( µb.userSettings.logRequests ) {
r.blockedRequests = prepareRequests(true, hasher);
r.allowedRequests = prepareRequests(false, hasher);
}
r.hash = hasher.end();
return r;
};
/******************************************************************************/
@ -800,8 +783,8 @@ var getPageDetails = function(µb, tabId) {
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
case 'getTabForStats':
vAPI.tabs.get(request.tabId, callback);
case 'getPageDetails':
getPageDetails(callback);
return;
default:
@ -812,14 +795,6 @@ var onMessage = function(request, sender, callback) {
var response;
switch ( request.what ) {
case 'getPageSelectors':
response = Object.keys(µb.pageStores);
break;
case 'getPageDetails':
response = getPageDetails(µb, request.tabId);
break;
default:
return vAPI.messaging.UNHANDLED;
}
@ -933,6 +908,52 @@ vAPI.messaging.listen('about.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// devtool-log.js
(function() {
'use strict';
/******************************************************************************/
var µb = µBlock;
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'readLogBuffer':
var pageStore = µb.pageStoreFromTabId(request.tabId);
if ( pageStore ) {
response = pageStore.logBuffer.readAll();
}
break;
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('devtool-log.js', onMessage);
/******************************************************************************/
})();
// https://www.youtube.com/watch?v=3_WcygKJP1k
/******************************************************************************/

View file

@ -45,22 +45,194 @@ var µb = µBlock;
/******************************************************************************/
/******************************************************************************/
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.domain = details.requestDomain;
this.hostname = details.requestHostname;
this.type = details.requestType;
this.result = result;
return this;
};
/******************************************************************************/
LogEntry.prototype.dispose = function() {
this.url = this.domain = this.hostname = this.type = this.result = '';
if ( logEntryJunkyard.length < logEntryJunkyardMax ) {
logEntryJunkyard.push(this);
}
};
/******************************************************************************/
var LogBuffer = function() {
this.lastReadTime = 0;
this.size = 25;
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);
this.size += this.writePtr;
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);
/******************************************************************************/
/******************************************************************************/
// To mitigate memory churning
var netFilteringResultCacheEntryJunkyard = [];
var netFilteringResultCacheEntryJunkyardMax = 200;
/******************************************************************************/
var NetFilteringResultCacheEntry = function(result, type, flags) {
this.init(result, type, flags);
var NetFilteringResultCacheEntry = function(result, type) {
this.init(result, type);
};
/******************************************************************************/
NetFilteringResultCacheEntry.prototype.init = function(result, type, flags) {
NetFilteringResultCacheEntry.prototype.init = function(result, type) {
this.result = result;
this.type = type;
this.flags = flags;
this.time = Date.now();
};
@ -76,12 +248,12 @@ NetFilteringResultCacheEntry.prototype.dispose = function() {
/******************************************************************************/
NetFilteringResultCacheEntry.factory = function(result, type, flags) {
NetFilteringResultCacheEntry.factory = function(result, type) {
var entry = netFilteringResultCacheEntryJunkyard.pop();
if ( entry === undefined ) {
entry = new NetFilteringResultCacheEntry(result, type, flags);
entry = new NetFilteringResultCacheEntry(result, type);
} else {
entry.init(result, type, flags);
entry.init(result, type);
}
return entry;
};
@ -115,10 +287,11 @@ NetFilteringResultCache.factory = function() {
/******************************************************************************/
NetFilteringResultCache.prototype.init = function() {
this.uname = 'NetFilteringResultCache:' + uidGenerator++;
this.urls = {};
this.count = 0;
this.shelfLife = 60 * 1000;
this.timer = null;
this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this);
};
/******************************************************************************/
@ -130,10 +303,12 @@ NetFilteringResultCache.prototype.dispose = function() {
}
this.urls[key].dispose();
}
µBlock.asyncJobs.remove(this.uname);
this.uname = '';
this.urls = {};
this.count = 0;
if ( this.timer !== null ) {
clearTimeout(this.timer);
this.timer = null;
}
if ( netFilteringCacheJunkyard.length < netFilteringCacheJunkyardMax ) {
netFilteringCacheJunkyard.push(this);
}
@ -142,16 +317,17 @@ NetFilteringResultCache.prototype.dispose = function() {
/******************************************************************************/
NetFilteringResultCache.prototype.add = function(url, result, type, flags) {
NetFilteringResultCache.prototype.add = function(context, result) {
var url = context.requestURL;
var type = context.requestType;
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);
this.urls[url] = NetFilteringResultCacheEntry.factory(result, type);
if ( this.count === 0 ) {
this.pruneAsync();
}
@ -197,13 +373,14 @@ NetFilteringResultCache.prototype.prune = function() {
/******************************************************************************/
NetFilteringResultCache.prototype.pruneAsync = function() {
µBlock.asyncJobs.add(
this.uname,
null,
this.prune.bind(this),
this.shelfLife + 120000,
false
);
if ( this.timer === null ) {
this.timer = setTimeout(this.boundPruneAsyncCallback, this.shelfLife * 2);
}
};
NetFilteringResultCache.prototype.pruneAsyncCallback = function() {
this.timer = null;
this.prune();
};
/******************************************************************************/
@ -272,12 +449,6 @@ 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);
};
@ -318,10 +489,12 @@ PageStore.prototype.init = function(tabId, pageURL) {
this.perLoadBlockedRequestCount = 0;
this.perLoadAllowedRequestCount = 0;
this.skipLocalMirroring = false;
this.netFilteringCache = NetFilteringResultCache.factory();
if ( µb.userSettings.logRequests ) {
this.netFilteringCache.shelfLife = 30 * 60 * 1000;
// 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();
}
return this;
@ -374,6 +547,7 @@ PageStore.prototype.dispose = function() {
this.hostnameToCountMap = null;
this.disposeFrameStores();
this.netFilteringCache = this.netFilteringCache.dispose();
this.logBuffer = this.logBuffer.dispose();
if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) {
pageStoreJunkyard.push(this);
}
@ -422,27 +596,46 @@ PageStore.prototype.getNetFilteringSwitch = function() {
/******************************************************************************/
PageStore.prototype.filterRequest = function(context) {
var requestURL = context.requestURL;
if ( this.getNetFilteringSwitch() === false ) {
this.recordResult(context.requestType, requestURL, '');
this.cacheResult(context, '');
return '';
}
var entry = this.netFilteringCache.lookup(requestURL);
var entry = this.netFilteringCache.lookup(context.requestURL);
if ( entry !== undefined ) {
//console.debug('cache HIT: PageStore.filterRequest("%s")', requestURL);
//console.debug('cache HIT: PageStore.filterRequest("%s")', context.requestURL);
return entry.result;
}
var result = µb.filterRequest(context);
var result = '';
//console.debug('cache MISS: PageStore.filterRequest("%s")', requestURL);
this.recordResult(context.requestType, requestURL, 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.
var df = µb.dynamicNetFilteringEngine.clearRegisters();
df.evaluateCellZY(context.rootHostname, context.requestHostname, context.requestType);
if ( df.mustBlockOrAllow() ) {
result = df.toFilterString();
}
// TODO: send this to a dev-panel tool
//console.debug('[%s, %s] = "%s"', context.requestHostname, context.requestType, result);
// Static filtering never override dynamic filtering
if ( result === '' ) {
result = µb.staticNetFilteringEngine.matchString(context);
}
//console.debug('cache MISS: PageStore.filterRequest("%s")', context.requestURL);
this.cacheResult(context, result);
// console.debug('[%s, %s] = "%s"', context.requestHostname, context.requestType, result);
return result;
};
/******************************************************************************/
PageStore.prototype.cacheResult = function(context, result) {
var requestHostname = context.requestHostname;
if ( this.hostnameToCountMap.hasOwnProperty(requestHostname) === false ) {
this.hostnameToCountMap[requestHostname] = 0;
@ -453,25 +646,14 @@ PageStore.prototype.filterRequest = function(context) {
} else /* if ( c === 'b' ) */ {
this.hostnameToCountMap[requestHostname] += 0x00000001;
}
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);
if ( collapsibleRequestTypes.indexOf(context.requestType) !== -1 ) {
this.netFilteringCache.add(context, result);
}
};
/******************************************************************************/
PageStore.prototype.recordResult = function(requestType, requestURL, result) {
if ( collapsibleRequestTypes.indexOf(requestType) !== -1 || µb.userSettings.logRequests ) {
this.netFilteringCache.add(requestURL, result, requestType, 0);
}
};
// Cache only what is worth it if logging is disabled
// http://jsperf.com/string-indexof-vs-object
var collapsibleRequestTypes = 'image sub_frame object';
/******************************************************************************/
@ -493,7 +675,7 @@ PageStore.prototype.updateBadge = function() {
var iconStr = '';
if ( µb.userSettings.showIconBadge && netFiltering && this.perLoadBlockedRequestCount ) {
// Safari can't show formatted strings, only integers.
if (vAPI.safari) {
if ( vAPI.safari ) {
iconStr = this.perLoadBlockedRequestCount;
}
else {

View file

@ -29,8 +29,9 @@
/******************************************************************************/
var stats;
var dynaTypes = [
var popupData;
var dfPaneBuilt = false;
var dfTypes = [
'image',
'inline-script',
'1p-script',
@ -48,7 +49,7 @@ var scopeToSrcHostnameMap = {
var threePlus = '+++';
var threeMinus = '';
var sixSpace = '\u2007\u2007\u2007\u2007\u2007\u2007';
var dynaHotspots = null;
var dfHotspots = null;
var hostnameToSortableTokenMap = {};
/******************************************************************************/
@ -60,15 +61,15 @@ var messager = vAPI.messaging.channel('popup.js');
/******************************************************************************/
var cachePopupData = function(data) {
stats = {};
popupData = {};
scopeToSrcHostnameMap['.'] = '';
hostnameToSortableTokenMap = {};
if ( typeof data !== 'object' ) {
return stats;
return popupData;
}
stats = data;
scopeToSrcHostnameMap['.'] = stats.pageHostname || '';
var hostnameDict = stats.hostnameDict;
popupData = data;
scopeToSrcHostnameMap['.'] = popupData.pageHostname || '';
var hostnameDict = popupData.hostnameDict;
if ( typeof hostnameDict === 'object' ) {
var domain, prefix;
for ( var hostname in hostnameDict ) {
@ -76,14 +77,14 @@ var cachePopupData = function(data) {
continue;
}
domain = hostnameDict[hostname].domain;
if ( domain === stats.pageDomain ) {
if ( domain === popupData.pageDomain ) {
domain = '\u0020';
}
prefix = hostname.slice(0, 0 - domain.length);
hostnameToSortableTokenMap[hostname] = domain + prefix.split('.').reverse().join('.');
}
}
return stats;
return popupData;
};
/******************************************************************************/
@ -118,12 +119,15 @@ var addDynamicFilterRow = function(des) {
row.descendants('[data-des]').attr('data-des', des);
row.descendants('div > span:nth-of-type(1)').text(des);
var hnDetails = stats.hostnameDict[des] || {};
var hnDetails = popupData.hostnameDict[des] || {};
var isDomain = des === hnDetails.domain;
row.toggleClass('isDomain', isDomain);
if ( hnDetails.allowCount !== 0 ) {
touchedDomains[hnDetails.domain] = true;
row.addClass('wasTouched');
row.addClass('allowed');
}
if ( hnDetails.blockCount !== 0 ) {
row.addClass('blocked');
}
row.appendTo('#dynamicFilteringContainer');
@ -134,8 +138,8 @@ var addDynamicFilterRow = function(des) {
// of the popup, and the left pane will have a scrollbar if ever its
// height is larger than what is available.
if ( popupHeight === undefined ) {
popupHeight = uDom('body > div:nth-of-type(2)').nodeAt(0).offsetHeight;
uDom('body > div:nth-of-type(1)').css('height', popupHeight + 'px');
popupHeight = uDom('#panes > div:nth-of-type(1)').nodeAt(0).offsetHeight;
uDom('#panes > div:nth-of-type(2)').css('height', popupHeight + 'px');
}
return row;
};
@ -169,10 +173,10 @@ var syncDynamicFilterCell = function(scope, des, type, result) {
if ( scope !== '.' || type !== '*' ) {
return;
}
if ( stats.hostnameDict.hasOwnProperty(des) === false ) {
if ( popupData.hostnameDict.hasOwnProperty(des) === false ) {
return;
}
var hnDetails = stats.hostnameDict[des];
var hnDetails = popupData.hostnameDict[des];
var aCount = hnDetails.allowCount;
var bCount = hnDetails.blockCount;
if ( aCount === 0 && bCount === 0 ) {
@ -192,9 +196,9 @@ var syncDynamicFilterCell = function(scope, des, type, result) {
var syncAllDynamicFilters = function() {
var hasRule = false;
var rules = stats.dynamicFilterRules;
var rules = popupData.dynamicFilterRules;
var type, result;
var types = dynaTypes;
var types = dfTypes;
var i = types.length;
while ( i-- ) {
type = types[i];
@ -220,28 +224,30 @@ var syncAllDynamicFilters = function() {
}
uDom('#privacyInfo').text(vAPI.i18n('popupHitDomainCountPrompt').replace('{{count}}', Object.keys(touchedDomains).length));
if ( dfPaneBuilt !== true ) {
uDom('#dynamicFilteringContainer')
.on('click', 'span[data-src]', unsetDynamicFilterHandler)
.on('mouseenter', '[data-src]', mouseenterCellHandler)
.on('mouseleave', '[data-src]', mouseleaveCellHandler);
dfHotspots = uDom('#actionSelector')
.on('click', 'span', setDynamicFilterHandler)
.detach();
dfPaneBuilt = true;
}
};
/******************************************************************************/
var renderPopup = function(details) {
if ( !cachePopupData(details) ) {
return;
}
var renderPopup = function() {
uDom('#appname').text(popupData.appName);
uDom('#version').text(popupData.appVersion);
var hdr = uDom('#version');
hdr.nodes[0].previousSibling.textContent = details.appName;
hdr.html(hdr.html() + 'v' + details.appVersion);
var isHTTP = /^https?:\/\/[0-9a-z]/.test(stats.pageURL);
var isHTTP = /^https?:\/\/[0-9a-z]/.test(popupData.pageURL);
// Conditions for request log:
// - `http` or `https` scheme
// - logging of requests enabled
uDom('#gotoLog').toggleClass(
'enabled',
isHTTP && stats.logRequests
);
uDom('#gotoLog').toggleClass('enabled', isHTTP);
// Conditions for element picker:
// - `http` or `https` scheme
@ -251,8 +257,8 @@ var renderPopup = function(details) {
);
var or = vAPI.i18n('popupOr');
var blocked = stats.pageBlockedRequestCount;
var total = stats.pageAllowedRequestCount + blocked;
var blocked = popupData.pageBlockedRequestCount;
var total = popupData.pageAllowedRequestCount + blocked;
var html = [];
if ( total === 0 ) {
html.push('0');
@ -268,8 +274,8 @@ var renderPopup = function(details) {
}
uDom('#page-blocked').html(html.join(''));
blocked = stats.globalBlockedRequestCount;
total = stats.globalAllowedRequestCount + blocked;
blocked = popupData.globalBlockedRequestCount;
total = popupData.globalAllowedRequestCount + blocked;
html = [];
if ( total === 0 ) {
html.push('0');
@ -284,27 +290,28 @@ var renderPopup = function(details) {
);
}
//if ( stats.dynamicFilteringEnabled ) {
// Build dynamic filtering pane only if in use
if ( popupData.dfEnabled ) {
syncAllDynamicFilters();
//}
}
uDom('#total-blocked').html(html.join(''));
uDom('#switch .fa').toggleClass('off', stats.pageURL === '' || !stats.netFilteringSwitch);
uDom('body').toggleClass('dynamicFilteringEnabled', stats.dynamicFilteringEnabled);
uDom('#switch .fa').toggleClass('off', popupData.pageURL === '' || !popupData.netFilteringSwitch);
uDom('#panes').toggleClass('dfEnabled', popupData.dfEnabled);
};
/******************************************************************************/
var toggleNetFilteringSwitch = function(ev) {
if ( !stats || !stats.pageURL ) {
if ( !popupData || !popupData.pageURL ) {
return;
}
messager.send({
what: 'toggleNetFiltering',
url: stats.pageURL,
url: popupData.pageURL,
scope: ev.ctrlKey || ev.metaKey ? 'page' : '',
state: !uDom(this).toggleClass('off').hasClass('off'),
tabId: stats.tabId
tabId: popupData.tabId
});
};
@ -323,11 +330,11 @@ var gotoDashboard = function() {
/******************************************************************************/
var gotoStats = function() {
var gotoDevTools = function() {
messager.send({
what: 'gotoURL',
details: {
url: 'dashboard.html?tab=stats&which=' + stats.tabId,
url: 'devtools.html?tabId=' + popupData.tabId,
select: true,
index: -1
}
@ -339,7 +346,7 @@ var gotoStats = function() {
var gotoPick = function() {
messager.send({
what: 'gotoPick',
tabId: stats.tabId
tabId: popupData.tabId
});
window.open('','_self').close();
};
@ -366,42 +373,42 @@ var gotoLink = function(ev) {
/******************************************************************************/
var toggleDynamicFiltering = function(ev) {
var el = uDom('body');
el.toggleClass('dynamicFilteringEnabled');
var el = uDom('#panes');
popupData.dfEnabled = !popupData.dfEnabled;
messager.send({
what: 'userSettings',
name: 'dynamicFilteringEnabled',
value: el.hasClass('dynamicFilteringEnabled')
});
value: popupData.dfEnabled
}, renderPopup);
};
/******************************************************************************/
var mouseenterCellHandler = function() {
if ( uDom(this).hasClass('ownRule') === false ) {
dynaHotspots.appendTo(this);
dfHotspots.appendTo(this);
}
};
var mouseleaveCellHandler = function() {
dynaHotspots.detach();
dfHotspots.detach();
};
/******************************************************************************/
var setDynamicFilter = function(src, des, type, action) {
// This can happen on pages where uBlock does not work
if ( typeof stats.pageHostname !== 'string' || stats.pageHostname === '' ) {
if ( typeof popupData.pageHostname !== 'string' || popupData.pageHostname === '' ) {
return;
}
var onDynamicFilterChanged = function(details) {
cachePopupData(details);
var onDynamicFilterChanged = function(response) {
cachePopupData(response);
syncAllDynamicFilters();
};
messager.send({
what: 'toggleDynamicFilter',
tabId: stats.tabId,
pageHostname: stats.pageHostname,
tabId: popupData.tabId,
pageHostname: popupData.pageHostname,
srcHostname: src,
desHostname: des,
requestType: type,
@ -414,12 +421,12 @@ var setDynamicFilter = function(src, des, type, action) {
var unsetDynamicFilterHandler = function() {
var cell = uDom(this);
setDynamicFilter(
cell.attr('data-src') === '/' ? '*' : stats.pageHostname,
cell.attr('data-src') === '/' ? '*' : popupData.pageHostname,
cell.attr('data-des'),
cell.attr('data-type'),
0
);
dynaHotspots.appendTo(cell);
dfHotspots.appendTo(cell);
};
/******************************************************************************/
@ -440,30 +447,12 @@ var setDynamicFilterHandler = function() {
action = 1;
}
setDynamicFilter(
cell.attr('data-src') === '/' ? '*' : stats.pageHostname,
cell.attr('data-src') === '/' ? '*' : popupData.pageHostname,
cell.attr('data-des'),
cell.attr('data-type'),
action
);
dynaHotspots.detach();
};
/******************************************************************************/
var installEventHandlers = function() {
uDom('h1,h2,h3,h4').on('click', gotoDashboard);
uDom('#switch .fa').on('click', toggleNetFilteringSwitch);
uDom('#gotoLog').on('click', gotoStats);
uDom('#gotoPick').on('click', gotoPick);
uDom('a[href^=http]').on('click', gotoLink);
uDom('#dynamicFilteringToggler').on('click', toggleDynamicFiltering);
uDom('#dynamicFilteringContainer').on('click', 'span[data-src]', unsetDynamicFilterHandler);
uDom('#dynamicFilteringContainer')
.on('mouseenter', '[data-src]', mouseenterCellHandler)
.on('mouseleave', '[data-src]', mouseleaveCellHandler);
dynaHotspots = uDom('#actionSelector');
dynaHotspots.on('click', 'span', setDynamicFilterHandler).detach();
dfHotspots.detach();
};
/******************************************************************************/
@ -471,8 +460,18 @@ var installEventHandlers = function() {
// Make menu only when popup html is fully loaded
uDom.onLoad(function() {
messager.send({ what: 'activeTabStats' }, renderPopup);
installEventHandlers();
messager.send({ what: 'activeTabStats' }, function(response) {
if ( !cachePopupData(response) ) {
return;
}
renderPopup();
});
uDom('h1,h2,h3,h4').on('click', gotoDashboard);
uDom('#switch .fa').on('click', toggleNetFilteringSwitch);
uDom('#gotoLog').on('click', gotoDevTools);
uDom('#gotoPick').on('click', gotoPick);
uDom('a[href^=http]').on('click', gotoLink);
uDom('#dfToggler').on('click', toggleDynamicFiltering);
});
/******************************************************************************/

View file

@ -1,255 +0,0 @@
/*******************************************************************************
µ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 uDom */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
var messager = vAPI.messaging.channel('stats.js');
/******************************************************************************/
var logSettingChanged = function() {
messager.send({
what: 'userSettings',
name: 'logRequests',
value: this.checked
});
uDom('#requests').toggleClass('logEnabled', this.checked);
renderPageSelector();
};
/******************************************************************************/
var cachedPageSelectors = {};
var cachedPageHash = '';
var toPrettyTypeNames = {
'stylesheet': 'css',
'sub_frame': 'frame',
'object': 'plugin',
'xmlhttprequest': 'XHR'
};
/******************************************************************************/
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 chunkSize = 50;
// make a regex out of the filter
var reText = filter;
var pos = reText.indexOf('$');
if ( pos > 0 ) {
reText = reText.slice(0, pos);
}
if ( reText.charAt(0) === 's' ) {
reText = reText.slice(3);
}
if ( reText === '*' ) {
reText = '\\*';
} else {
reText = reText
.replace(/\./g, '\\.')
.replace(/\?/g, '\\?')
.replace('||', '')
.replace(/\^/g, '.')
.replace(/\*/g, '.*')
;
}
var re = new RegExp(reText, 'gi');
var matches = re.exec(url);
var renderedURL = chunkify(url);
if ( matches && matches[0].length ) {
var index = (re.lastIndex / chunkSize) | 0;
var offset = re.lastIndex % chunkSize;
if ( index > 0 && offset === 0 ) {
offset = chunkSize;
index -= 1;
}
var segment = renderedURL[index];
renderedURL[index] = segment.slice(0, offset) + '</b>' + segment.slice(offset);
index = (matches.index / chunkSize) | 0;
offset = matches.index % chunkSize;
if ( index > 0 && offset === 0 ) {
offset = chunkSize;
index -= 1;
}
segment = renderedURL[index];
renderedURL[index] = segment.slice(0, offset) + '<b>' + segment.slice(offset);
}
return renderedURL.join('\n');
};
/******************************************************************************/
var renderPageDetails = function(tabId) {
if ( !cachedPageSelectors[tabId] ) {
return;
}
var onDataReceived = function(details) {
if ( details.hash === cachedPageHash ) {
return;
}
cachedPageHash = details.hash;
var renderRequests = function(requests, className) {
requests.sort(function(a, b) {
var r = a.domain.localeCompare(b.domain);
if ( r ) { return r; }
r = a.reason.localeCompare(b.reason);
if ( r ) { return r; }
r = a.type.localeCompare(b.type);
if ( r ) { return r; }
return a.url.localeCompare(b.url);
});
var html = [], request;
html.push(
'<tr class="header ', className, '">',
'<td colspan="4"><h3>',
vAPI.i18n(className + (requests.length ? 'RequestsHeader' : 'RequestsEmpty')),
'</h3>'
);
var currentDomain = '';
for ( var i = 0; i < requests.length; i++ ) {
request = requests[i];
if ( request.domain !== currentDomain ) {
currentDomain = request.domain;
html.push(
'<tr class="', className, ' domainHeader">',
'<td colspan="4">', currentDomain
);
}
html.push(
'<tr class="', className, request.flags & 0x01 ? ' logMirrored': '', ' requestEntry">',
'<td>',
'<td>', toPrettyTypeNames[request.type] || request.type,
'<td>', renderURL(request.url, request.reason),
'<td>', chunkify(request.reason).join('\n')
);
}
return html;
};
uDom('#requests .tableHeader ~ tr').remove();
var htmlBlocked = renderRequests(details.blockedRequests || [], 'logBlocked');
var htmlAllowed = renderRequests(details.allowedRequests || [], 'logAllowed');
uDom('#requests .tableHeader').insertAfter(htmlBlocked.concat(htmlAllowed).join(''));
};
messager.send({ what: 'getPageDetails', tabId: tabId }, onDataReceived);
};
/******************************************************************************/
var pageSelectorChanged = function() {
renderPageDetails(this.value);
};
/******************************************************************************/
var renderPageSelector = function(targetTabId) {
if ( !uDom('#logRequests').prop('checked') ) {
return;
}
var selectedTabId = targetTabId || parseInt(uDom('#pageSelector').val(), 10);
var onTabReceived = function(tab) {
if ( !tab ) {
return;
}
var html = [
'<option value="',
tab.id,
'">',
tab.title
];
uDom('#pageSelector').append(html.join(''));
if ( tab.id === selectedTabId ) {
uDom('#pageSelector').val(tab.id);
}
};
var onDataReceived = function(pageSelectors) {
uDom('#requests').toggleClass('empty', pageSelectors.length === 0);
uDom('#pageSelector option').remove();
cachedPageSelectors = {};
pageSelectors.sort().map(function(tabId) {
cachedPageSelectors[tabId] = true;
});
if ( !cachedPageSelectors[selectedTabId] ) {
selectedTabId = pageSelectors[0];
}
for ( var i = 0; i < pageSelectors.length; i++ ) {
messager.send({
what: 'getTabForStats',
tabId: parseInt(pageSelectors[i], 10)
}, onTabReceived);
}
if ( pageSelectors.length > 0 ) {
renderPageDetails(selectedTabId);
}
};
messager.send({ what: 'getPageSelectors' }, onDataReceived);
};
/******************************************************************************/
var onUserSettingsReceived = function(details) {
uDom('#logRequests').prop('checked', details.logRequests);
uDom('#requests').toggleClass('logEnabled', details.logRequests);
var matches = window.location.search.slice(1).match(/(?:^|&)which=(\d+)/);
var tabId = matches && matches.length === 2 ? parseInt(matches[1], 10) : 0;
renderPageSelector(tabId);
uDom('#logRequests').on('change', logSettingChanged);
uDom('#refresh').on('click', function() { renderPageSelector(); });
uDom('#pageSelector').on('change', pageSelectorChanged);
};
/******************************************************************************/
uDom.onLoad(function() {
messager.send({ what: 'userSettings' }, onUserSettingsReceived);
});
/******************************************************************************/
})();

View file

@ -661,6 +661,10 @@
µb.mirrors.toggle(settings.experimentalEnabled);
µb.contextMenu.toggle(settings.contextMenuEnabled);
// Remove obsolete setting
delete µb.userSettings.logRequests;
µb.XAL.keyvalRemoveOne('logRequests');
if ( typeof settings.dynamicFilteringSelfie === 'string' ) {
if ( settings.dynamicFilteringString === '' && settings.dynamicFilteringSelfie !== '' ) {
µb.dynamicNetFilteringEngine.fromObsoleteSelfie(settings.dynamicFilteringSelfie);

View file

@ -83,7 +83,12 @@ vAPI.tabs.onPopup = function(details) {
// https://github.com/gorhill/uBlock/issues/91
if ( result !== '' ) {
pageStore.recordResult('popup', requestURL, result);
var context = {
requestURL: requestURL,
requestHostname: µb.URI.hostnameFromURI(requestURL),
requestType: 'popup'
};
pageStore.logBuffer.writeOne(context, result);
}
// Not blocked

View file

@ -95,6 +95,9 @@ var onBeforeRequest = function(details) {
var result = pageStore.filterRequest(requestContext);
// Log result
pageStore.logBuffer.writeOne(requestContext, result);
// Not blocked
if ( pageStore.boolFromResult(result) === false ) {
//console.debug('onBeforeRequest()> ALLOW "%s" (%o) because "%s"', details.url, details, result);
@ -125,7 +128,6 @@ var onBeforeRequest = function(details) {
// Not all redirects will succeed, until bug above is fixed.
var redirectURL = µb.mirrors.toURL(requestURL, true);
if ( redirectURL !== '' ) {
pageStore.setRequestFlags(requestURL, 0x01, 0x01);
//console.debug('"%s" redirected to "%s..."', requestURL.slice(0, 50), redirectURL.slice(0, 50));
return { redirectUrl: redirectURL };
}

View file

@ -321,75 +321,7 @@ var matchWhitelistDirective = function(url, hostname, directive) {
this.XAL.keyvalSetOne('dynamicFilteringString', this.userSettings.dynamicFilteringString);
// https://github.com/gorhill/uBlock/issues/420
if ( details.requestType === '3p-frame' && !details.block ) {
this.cosmeticFilteringEngine.removeFromSelectorCache(details.hostname, 'net');
}
};
/******************************************************************************/
µBlock.isFirstParty = function(firstPartyDomain, hostname) {
if ( hostname.slice(0 - firstPartyDomain.length) !== firstPartyDomain ) {
return false;
}
// Be sure to not confuse 'example.com' with 'anotherexample.com'
var c = hostname.charAt(hostname.length - firstPartyDomain.length - 1);
return c === '.' || c === '';
};
/******************************************************************************/
// The core logic to evaluate requests through dynamic/static filtering
// is here.
µBlock.filterRequest = function(context) {
// 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.
// Dynamic filtering evaluation is ordered from most-specific to least-
// specific.
var df = this.dynamicNetFilteringEngine.clearRegisters();
var rootHostname = context.rootHostname;
var requestHostname = context.requestHostname;
var requestType = context.requestType;
// Dynamic filters:
// 1. specific source, specific destination, any type, allow/block
// 2. any source, specific destination, any type, allow/block
df.evaluateCellZY(rootHostname, requestHostname, '*');
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
// Dynamic filters:
// 3. specific source, any destination, specific type, allow/block
// 4. any source, any destination, specific type, allow/block
if ( df.mustAbort() === false ) {
if ( requestType === 'script' ) {
df.evaluateCellZY(rootHostname, requestHostname, this.isFirstParty(rootHostname, requestHostname) ? '1p-script' : '3p-script');
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
} else if ( requestType === 'sub_frame' ) {
if ( this.isFirstParty(rootHostname, requestHostname) === false ) {
df.evaluateCellZY(rootHostname, requestHostname, '3p-frame');
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
}
} else {
df.evaluateCellZY(rootHostname, requestHostname, requestType);
if ( df.mustBlockOrAllow() ) {
return df.toFilterString();
}
}
}
// 5. Static filtering never override dynamic filtering
return this.staticNetFilteringEngine.matchString(context);
this.cosmeticFilteringEngine.removeFromSelectorCache(details.srcHostname, 'net');
};
/******************************************************************************/
@ -403,6 +335,7 @@ var matchWhitelistDirective = function(url, hostname, directive) {
µBlock.isAllowResult = function(result) {
return typeof result !== 'string' || result.charAt(1) !== 'b';
};
/******************************************************************************/
})();

View file

@ -9,20 +9,12 @@
</head>
<body>
<h4 title="popupTipDashboard">v<span id="version"></span></h4>
<div>
<div id="dynamicFilteringContainer">
<div><span data-i18n="popupImageRulePrompt"></span><span data-src="/" data-des="*" data-type="image"> </span><span data-src="." data-des="*" data-type="image"></span></div>
<div><span data-i18n="popupInlineScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="inline-script"> </span><span data-src="." data-des="*" data-type="inline-script"> </span></div>
<div><span data-i18n="popup1pScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="1p-script"> </span><span data-src="." data-des="*" data-type="1p-script"> </span></div>
<div><span data-i18n="popup3pScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="3p-script"> </span><span data-src="." data-des="*" data-type="3p-script"> </span></div>
<div><span data-i18n="popup3pFrameRulePrompt"></span><span data-src="/" data-des="*" data-type="3p-frame"> </span><span data-src="." data-des="*" data-type="3p-frame"> </span></div>
<div id="privacyInfo"></div>
</div>
</div><!-- DO NOT REMOVE --><div>
<h4 title="popupTipDashboard"><span id="appname"></span><span id="version"></span></h4>
<div id="panes">
<div>
<p id="switch" data-i18n-tip="popupPowerSwitchInfo"><span class="fa">&#xf011;</span></p>
<p id="switch-hint"></p>
<p id="dynamicFilteringToggler" data-i18n="popupBlockedRequestPrompt"></p>
<p id="dfToggler" data-i18n="popupBlockedRequestPrompt"></p>
<p class="stats">
<span data-i18n="popupBlockedOnThisPagePrompt"></span>&ensp;
<span id="gotoPick" class="fa tool" data-i18n-tip="popupTipPicker" data-tip-anchor="top">&#xf1fb;</span>&ensp;
@ -31,11 +23,22 @@
<p id="page-blocked">?</p>
<p class="stats" data-i18n="popupBlockedSinceInstallPrompt"></p>
<p id="total-blocked">?</p>
</div><!-- DO NOT REMOVE --><div>
<div id="dynamicFilteringContainer">
<div><span data-i18n="popupImageRulePrompt"></span><span data-src="/" data-des="*" data-type="image"> </span><span data-src="." data-des="*" data-type="image"></span></div>
<div><span data-i18n="popupInlineScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="inline-script"> </span><span data-src="." data-des="*" data-type="inline-script"> </span></div>
<div><span data-i18n="popup1pScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="1p-script"> </span><span data-src="." data-des="*" data-type="1p-script"> </span></div>
<div><span data-i18n="popup3pScriptRulePrompt"></span><span data-src="/" data-des="*" data-type="3p-script"> </span><span data-src="." data-des="*" data-type="3p-script"> </span></div>
<div><span data-i18n="popup3pFrameRulePrompt"></span><span data-src="/" data-des="*" data-type="3p-frame"> </span><span data-src="." data-des="*" data-type="3p-frame"> </span></div>
<div id="privacyInfo"></div>
</div>
</div>
</div>
<div id="templates" style="display: none">
<div><span></span><span data-src="/" data-des="" data-type="*"> </span><span data-src="." data-des="" data-type="*"> </span></div>
<div id='actionSelector'><span id="dynaAllow"></span><span id="dynaNoop"></span><span id="dynaBlock"></span></div>
<div id=hotspotTip></div>
</div>
<script src="js/vapi-common.js"></script>

View file

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>µBlock — Statistics</title>
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
<link rel="stylesheet" type="text/css" href="css/stats.css">
</head>
<body>
<ul>
<li><input id="logRequests" type="checkbox" data-range="bool" /><label data-i18n="logNetRequestsPrompt" for="logRequests"></label>
<button class="whatisthis"></button>
<div class="whatisthis-expandable para" data-i18n="logNetRequestsHelp"></div>
</ul>
<div id="requests">
<span id="refresh" class="fa">&#xf021;</span><select id="pageSelector"></select>
<table>
<tr class="tableHeader">
<th>
<th data-i18n="logRequestsHeaderType">
<th data-i18n="logRequestsHeaderURL">
<th data-i18n="logRequestsHeaderFilter">
</table>
</div>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/stats.js"></script>
</body>
</html>