diff --git a/_locales/de/messages.json b/_locales/de/messages.json
index fc58f74..5bcebbb 100644
--- a/_locales/de/messages.json
+++ b/_locales/de/messages.json
@@ -184,6 +184,30 @@
"message": "No net traffic seen for this tab so far.",
"description": "..."
},
+ "matrixABPButtonTip" : {
+ "message": "Requests blocked through \u000a ABP filtering on this page: {{abpCount}}.\u000a Click to turn on/off ABP \u000a filtering for this scope.",
+ "description": "Tool tip for ABP button: {{}} is a placeholder for number of blocked requests."
+ },
+ "matrixPersistButtonTip" : {
+ "message": "Save all temporary permissions \u000a for this scope.",
+ "description": "Tool tip for the persist button"
+ },
+ "matrixRevertButtonTip" : {
+ "message": "Remove temporary \u000a permissions for this \u000a scope.",
+ "description": "Tool tip for the revert local permission button"
+ },
+ "matrixRevertButtonAllTip" : {
+ "message": "Remove all temporary \u000a permissions.",
+ "description": "Tool tip for the revert all permissions button"
+ },
+ "matrixReloadButton" : {
+ "message": "Reload the page.",
+ "description": "Tool tip for the reload button"
+ },
+ "matrixPowerButton" : {
+ "message": "Disable/enable HTTP Switchboard.",
+ "description": "Tool tip for the 'power' button"
+ },
"statsPageTitle" : {
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 6d8ef6a..7548417 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -184,6 +184,30 @@
"message": "No net traffic seen for this tab so far.",
"description": "..."
},
+ "matrixABPButtonTip" : {
+ "message": "Requests blocked through \u000a ABP filtering on this page: {{abpCount}}.\u000a Click to turn on/off ABP \u000a filtering for this scope.",
+ "description": "Tool tip for ABP button: {{}} is a placeholder for number of blocked requests."
+ },
+ "matrixPersistButtonTip" : {
+ "message": "Save all temporary permissions \u000a for this scope.",
+ "description": "Tool tip for the persist button"
+ },
+ "matrixRevertButtonTip" : {
+ "message": "Remove temporary \u000a permissions for this \u000a scope.",
+ "description": "Tool tip for the revert local permission button"
+ },
+ "matrixRevertButtonAllTip" : {
+ "message": "Remove all temporary \u000a permissions.",
+ "description": "Tool tip for the revert all permissions button"
+ },
+ "matrixReloadButton" : {
+ "message": "Reload the page.",
+ "description": "Tool tip for the reload button"
+ },
+ "matrixPowerButton" : {
+ "message": "Disable/enable HTTP Switchboard.",
+ "description": "Tool tip for the 'power' button"
+ },
"statsPageTitle" : {
diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json
index e7d994d..98fce6e 100644
--- a/_locales/fr/messages.json
+++ b/_locales/fr/messages.json
@@ -184,6 +184,30 @@
"message": "Aucune activité réseau observée pour cet onglet jusqu'à présent.",
"description": "..."
},
+ "matrixABPButtonTip" : {
+ "message": "Requests blocked through \u000a ABP filtering on this page: {{abpCount}}.\u000a Click to turn on/off ABP \u000a filtering for this scope.",
+ "description": "Tool tip for ABP button: {{}} is a placeholder for number of blocked requests."
+ },
+ "matrixPersistButtonTip" : {
+ "message": "Save all temporary permissions \u000a for this scope.",
+ "description": "Tool tip for the persist button"
+ },
+ "matrixRevertButtonTip" : {
+ "message": "Remove temporary \u000a permissions for this \u000a scope.",
+ "description": "Tool tip for the revert local permission button"
+ },
+ "matrixRevertButtonAllTip" : {
+ "message": "Remove all temporary \u000a permissions.",
+ "description": "Tool tip for the revert all permissions button"
+ },
+ "matrixReloadButton" : {
+ "message": "Reload the page.",
+ "description": "Tool tip for the reload button"
+ },
+ "matrixPowerButton" : {
+ "message": "Disable/enable HTTP Switchboard.",
+ "description": "Tool tip for the 'power' button"
+ },
"statsPageTitle" : {
diff --git a/css/popup.css b/css/popup.css
index f24542b..5b22f5e 100644
--- a/css/popup.css
+++ b/css/popup.css
@@ -25,17 +25,34 @@ body {
padding-top: 5.5em;
}
-.pullright {
- float: right;
- }
.toolbar {
- padding-bottom: 0.5em;
+ margin: 0;
+ border: 0;
+ padding: 0;
+ padding-bottom: 0.25em;
display: inline-block;
white-space: nowrap;
vertical-align: top;
+ position: absolute;
+ top: 0;
}
-.toolbar button {
+.toolbar.pullLeft {
+ left: 0;
+ }
+.toolbar.pullRight {
+ right: 0;
+ }
+
+
+body.powerOff #toolbarLeft {
+ visibility: hidden;
+ }
+
+
+
+
+body .toolbar button {
margin: 0;
border: 0;
padding: 4px;
@@ -43,32 +60,46 @@ body {
background-color: white;
opacity: 0.9;
cursor: pointer;
+ vertical-align: top;
}
-.toolbar button:hover {
+body .toolbar button:hover {
background-color: #eee;
opacity: 1;
}
-.toolbar button[disabled] {
+body .toolbar button[disabled] {
+ color: #ccc;
+ }
+body .toolbar button.disabled {
color: #ccc;
}
-.toolbar button.fa {
+body .toolbar button.fa {
font: 1.75em 'FontAwesome';
min-width: 1.1em;
}
+body.tScopeGlobal .toolbar button.scopeRel:not(.disabled) {
+ color: #000;
+ }
+body.tScopeDomain .toolbar button.scopeRel:not(.disabled) {
+ color: #24c;
+ }
+body.tScopeSite .toolbar button.scopeRel:not(.disabled) {
+ color: #48c;
+ }
+body.powerOff #toolbarLeft {
+ visibility: hidden;
+ }
+
.dropdown-menu {
margin: 0;
border: 0;
padding: 3px 0 0 0;
position: absolute;
- z-index: 1000;
+ z-index: 50;
font-size: 110%;
display: none;
white-space: normal;
}
-.dropdown-menu.pullright {
- right: 0;
- }
.dropdown-menu > ul {
margin: 0;
border: 0;
@@ -115,28 +146,12 @@ body {
background-color: transparent;
opacity: 0.1;
display: none;
- z-index: 900;
+ z-index: 40;
}
.dropdown-menu.show ~ .dropdown-menu-capture {
display: block;
}
-body.tScopeGlobal #buttonPersist {
- color: #000;
- }
-body.tScopeDomain #buttonPersist {
- color: #24c;
- }
-body.tScopeSite #buttonPersist {
- color: #48c;
- }
-body #buttonRevertScope {
- }
-body.powerOff #buttonPersist,
-body.powerOff #buttonRevertScope,
-body.powerOff #buttonRevertAll {
- visibility: hidden;
- }
body #buttonRevertAll {
position: relative;
color: transparent;
@@ -163,25 +178,27 @@ body #buttonRevertAll > span:nth-of-type(4) {
bottom: 3px;
}
-#buttonPresets {
+button {
position: relative;
}
-#buttonPresets > span {
- padding: 0 2px;
+button > span.badge {
+ padding: 1px 1px;
+ display: inline-block;
font-size: 40%;
position: absolute;
right: 1px;
bottom: 1px;
color: #000;
- background-color: #fff;
- opacity: 0.75;
+ background-color: rgba(240,240,240,0.75)
}
-button[disabled] > span {
+button[disabled] > span.badge,
+button.disabled > span.badge {
display: none;
}
#buttonPresets + .dropdown-menu {
- width: 80%;
- left: 10%;
+ position: fixed;
+ left: 10vw;
+ width: 80vw;
}
body.powerOff #buttonPresets {
visibility: hidden;
@@ -217,18 +234,6 @@ body.powerOff #buttonPower i {
color: #c00;
}
-body #toolbarScope {
- margin: 0;
- border: 0;
- padding: 0;
- display: inline-block;
- box-sizing: content-box;
- position: relative;
- }
-body.powerOff #toolbarScope {
- visibility: hidden;
- }
-
body .toolbar button#scopeCell {
margin: 0;
border: 1px dotted rgba(0,0,0,0.2);
@@ -243,6 +248,13 @@ body .toolbar button#scopeCell {
background-repeat: no-repeat;
background-position: -1px -1px;
}
+body #scopeCell + .dropdown-menu {
+ padding: 6px 1px 3px 1px;
+ left: 0;
+ right: 0;
+ text-align: right;
+ width: 16em;
+ }
body.tScopeGlobal #scopeCell {
background-color: #000;
}
@@ -262,12 +274,6 @@ body.pScopeSite #scopeCell {
background-image: url('/img/permanent-scope-site.png');
}
-body #scopeKeys {
- padding: 6px 1px 3px 1px;
- left: 0;
- right: 0;
- text-align: right;
- }
.matrix {
text-align: left;
@@ -309,8 +315,8 @@ body #scopeKeys {
direction: rtl;
}
.matrix .matRow.l2 > .matCell:first-child {
- margin-left: 3px;
- width: calc(16em - 3px);
+ margin-left: 1px;
+ width: calc(16em - 1px);
}
.matrix .matRow > .matCell:hover {
border-style: solid;
@@ -553,3 +559,33 @@ body.powerOff #whitelist, body.powerOff #blacklist {
opacity: 1;
}
+
+/* http://stackoverflow.com/questions/2011142/how-to-change-the-style-of-title-attribute-inside-the-anchor-tag?answertab=votes */
+*[data-tip] {
+ position: relative;
+ cursor: pointer;
+ }
+*[data-tip]:hover:after {
+ position: absolute;
+ content: attr(data-tip);
+ padding: 4px 6px;
+ font-size: 10px;
+ line-height: 150%;
+ text-align: left;
+ color: black;
+ background-color: white;
+ top: 110%;
+ white-space: pre;
+ z-index: 20;
+ border: 1px solid gray;
+ border-radius: 3px;
+ box-shadow: 1px 1px 3px gray;
+ }
+
+.pullLeft *[data-tip]:hover:after {
+ left: 0;
+ }
+.pullRight *[data-tip]:hover:after {
+ right: 0;
+ }
+
diff --git a/info.html b/info.html
index 717e557..8cb5ba7 100644
--- a/info.html
+++ b/info.html
@@ -44,8 +44,12 @@
#requests tr.blocked-true {
color: #c00;
}
+#requests tr:first-child {
+ font-weight: bold;
+ background-color: #eee;
+ }
#requests tr > td {
- padding: 1px 1em 1px 0;
+ padding: 1px 0.75em 1px 0;
white-space: nowrap;
}
#requests tr > td:nth-of-type(2) {
@@ -54,6 +58,26 @@
.type-main_frame {
font-weight: bold;
}
+
+/* http://stackoverflow.com/questions/2011142/how-to-change-the-style-of-title-attribute-inside-the-anchor-tag?answertab=votes */
+*[data-tip] {
+ position: relative;
+ cursor: pointer;
+ }
+*[data-tip]:hover:after {
+ content: attr(data-tip);
+ padding: 8px;
+ color: black;
+ background-color: white;
+ position: absolute;
+ left: -25%;
+ bottom: 120%;
+ white-space: nowrap;
+ z-index: 20px;
+ border: 1px solid gray;
+ border-radius: 3px;
+ box-shadow: 1px 1px 3px gray;
+ }
@@ -67,7 +91,7 @@
Generic stats
Local cookies removed:
Local storages emptied:
Browser caches cleared:
-Requests blocked by Adblock+ complex filters: ? (?% of all blocked requests)
+Requests blocked by Adblock+ complex filters: ? (?% of all blocked requests)
@@ -133,7 +157,8 @@
- ... | | <a> | |
+ when | what | | | where |
+ | | <a> | | |
diff --git a/js/abp-filters.js b/js/abp-filters.js
index 0cc3b06..84af40c 100644
--- a/js/abp-filters.js
+++ b/js/abp-filters.js
@@ -352,7 +352,7 @@ var matchStringToFilterChain = function(f, s, tokenBeg) {
while ( f !== undefined ) {
if ( f.match(s, tokenBeg) ) {
// console.log('abp-filters.js> matchStringToFilterChain(): "%s" matches "%s"', f.s, s);
- return true;
+ return f.s;
}
f = f.next;
}
@@ -362,7 +362,13 @@ var matchStringToFilterChain = function(f, s, tokenBeg) {
/******************************************************************************/
var matchString = function(s) {
- var fidx = filterIndex;
+ // rhill 2014-03-12: need to skip ABP filtering if HTTP is turned off.
+ // https://github.com/gorhill/httpswitchboard/issues/208
+ if ( HTTPSB.off ) {
+ return false;
+ }
+
+ var fidx = filterIndex, f;
var matches;
var token;
var tokenBeg, tokenEnd;
@@ -379,31 +385,37 @@ var matchString = function(s) {
if ( suffixKey.length > 1 ) {
if ( prefixKey !== '' ) {
- if ( matchFn(fidx[prefixKey + token + suffixKey], s, tokenBeg) ) {
- return true;
+ f = matchFn(fidx[prefixKey + token + suffixKey], s, tokenBeg);
+ if ( f !== false ) {
+ return f;
}
}
- if ( matchFn(fidx[token + suffixKey], s, tokenBeg) ) {
- return true;
+ f = matchFn(fidx[token + suffixKey], s, tokenBeg);
+ if ( f !== false ) {
+ return f;
}
}
if ( suffixKey !== '' ) {
if ( prefixKey !== '' ) {
- if ( matchFn(fidx[prefixKey + token + suffixKey.charAt(0)], s, tokenBeg) ) {
- return true;
+ f = matchFn(fidx[prefixKey + token + suffixKey.charAt(0)], s, tokenBeg);
+ if ( f !== false ) {
+ return f;
}
}
- if ( matchFn(fidx[token + suffixKey.charAt(0)], s, tokenBeg) ) {
- return true;
+ f = matchFn(fidx[token + suffixKey.charAt(0)], s, tokenBeg);
+ if ( f !== false ) {
+ return f;
}
}
if ( prefixKey !== '' ) {
- if ( matchFn(fidx[prefixKey + token], s, tokenBeg) ) {
- return true;
+ f = matchFn(fidx[prefixKey + token], s, tokenBeg);
+ if ( f !== false ) {
+ return f;
}
}
- if ( matchFn(fidx[token], s, tokenBeg) ) {
- return true;
+ f = matchFn(fidx[token], s, tokenBeg);
+ if ( f !== false ) {
+ return f;
}
}
diff --git a/js/background.js b/js/background.js
index 75e7aef..4b7984e 100644
--- a/js/background.js
+++ b/js/background.js
@@ -134,7 +134,7 @@ var HTTPSB = {
browserCacheClearedCounter: 0,
storageQuota: chrome.storage.local.QUOTA_BYTES,
storageUsed: 0,
- abpHitCount: 0,
+ abpBlockCount: 0,
// internal state
webRequestHandler: false,
diff --git a/js/cookies.js b/js/cookies.js
index f097383..2e002ac 100644
--- a/js/cookies.js
+++ b/js/cookies.js
@@ -154,9 +154,11 @@ var cookieHunter = {
// rhill 2013-11-20:
// https://github.com/gorhill/httpswitchboard/issues/60
// Need to URL-encode cookie name
- pageStats.recordRequest('cookie',
- this.cookieURLFromCookieEntry(cookieEntry) + '{cookie:' + encodeURIComponent(cookieEntry.name) + '}',
- block);
+ pageStats.recordRequest(
+ 'cookie',
+ this.cookieURLFromCookieEntry(cookieEntry) + '{cookie:' + encodeURIComponent(cookieEntry.name) + '}',
+ block
+ );
httpsb.requestStats.record('cookie', block);
// rhill 2013-11-21:
diff --git a/js/httpsb.js b/js/httpsb.js
index 802c1c5..2822402 100644
--- a/js/httpsb.js
+++ b/js/httpsb.js
@@ -563,6 +563,23 @@ HTTPSB.getPermanentColor = function(scopeKey, type, hostname) {
/******************************************************************************/
+HTTPSB.getTemporaryABPFiltering = function(scopeKey) {
+ if ( this.off ) {
+ return false;
+ }
+ return this.temporaryScopes.getABPFiltering(scopeKey);
+};
+
+HTTPSB.getPermanentABPFiltering = function(scopeKey) {
+ return this.permanentScopes.getABPFiltering(scopeKey);
+};
+
+HTTPSB.toggleTemporaryABPFiltering = function(scopeKey, state) {
+ return this.temporaryScopes.toggleABPFiltering(scopeKey, state);
+};
+
+/******************************************************************************/
+
// Commit temporary permissions.
HTTPSB.commitPermissions = function(persist) {
@@ -599,6 +616,28 @@ HTTPSB.revertScopeRules = function(scopeKey) {
/******************************************************************************/
+HTTPSB.getTemporaryScopeDirtyCount = function(pageURL) {
+ var tScopeKey = this.temporaryScopeKeyFromPageURL(pageURL);
+ var tscope = this.temporaryScopeFromScopeKey(tScopeKey);
+ // This should not happen
+ if ( !tscope ) {
+ return 0;
+ }
+ // If there is no matching permanent scope, count is all
+ // items in temporary scope
+ var pscope = this.permanentScopeFromScopeKey(tScopeKey);
+ if ( !pscope ) {
+ return tscope.white.count +
+ tscope.black.count +
+ tscope.gray.count +
+ 2; // for new temporary scope and abpFiltering
+ }
+ // If there is a matching scope, return difference between both
+ return tscope.diff(pscope);
+};
+
+/******************************************************************************/
+
// save white/blacklist
HTTPSB.savePermissions = function() {
var bin = {
diff --git a/js/info.js b/js/info.js
index 72ca2c5..80c94d8 100644
--- a/js/info.js
+++ b/js/info.js
@@ -28,6 +28,13 @@
var targetUrl = 'All';
var maxRequests = 500;
+var tableFriendlyTypeNames = {
+ 'main_frame': 'page',
+ 'stylesheet': 'css',
+ 'sub_frame': 'frame',
+ 'xmlhttprequest': 'xhr'
+};
+
/******************************************************************************/
function gethttpsb() {
@@ -47,12 +54,12 @@ function updateRequestData() {
var pageUrls = targetUrl === 'All' ?
Object.keys(gethttpsb().pageStats) :
[targetUrl];
- var iPageUrl, nPageUrls, pageUrl;
+ var pageUrl;
var logEntries, i, n, logEntry;
var pageStats, pageRequests;
- nPageUrls = pageUrls.length;
- for ( iPageUrl = 0; iPageUrl < nPageUrls; iPageUrl++ ) {
+ var nPageUrls = pageUrls.length;
+ for ( var iPageUrl = 0; iPageUrl < nPageUrls; iPageUrl++ ) {
pageUrl = pageUrls[iPageUrl];
pageStats = pageStatsFromPageUrl(pageUrl);
// Unsure if it can happen... Just in case
@@ -157,8 +164,8 @@ function renderStats() {
'#cookieHeaderFoiledCounter': httpsb.cookieHeaderFoiledCounter,
'#refererHeaderFoiledCounter': httpsb.refererHeaderFoiledCounter,
'#browserCacheClearedCounter': httpsb.browserCacheClearedCounter,
- '#abpHitCount': httpsb.abpHitCount,
- '#abpHitRate': (httpsb.abpHitCount * 100 / httpsb.requestStats.blocked.all).toFixed(1),
+ '#abpBlockCount': httpsb.abpBlockCount,
+ '#abpBlockRate': (httpsb.abpBlockCount * 100 / httpsb.requestStats.blocked.all).toFixed(1),
'#blockedAllCount': requestStats.blocked.all,
'#blockedMainFrameCount': blockedStats.main_frame,
'#blockedCookieCount': blockedStats.cookie,
@@ -186,63 +193,77 @@ function renderStats() {
/******************************************************************************/
function renderRequestRow(row, request) {
+ var pos, text, attr;
var jqRow = $(row);
row = jqRow[0];
jqRow.attr('id', '');
jqRow.css('display', '');
jqRow.removeClass();
- if ( request.blocked ) {
+ jqRow.addClass('rendered');
+ if ( request.block !== false ) {
jqRow.addClass('blocked-true');
} else {
jqRow.addClass('blocked-false');
}
jqRow.addClass('type-' + request.type);
var cells = row.cells;
+
+ // when
var when = new Date(request.when);
$(cells[0]).text(when.toLocaleTimeString());
- $(cells[1]).text(request.type);
- var a = $('a', cells[2]);
+
+ // request type
+ text = tableFriendlyTypeNames[request.type] || request.type;
+ $(cells[1]).text(text);
+
// Well I got back full control since not using Tempo.js, I can now
// generate smarter hyperlinks, that is, not hyperlinking fake
// request URLs, which are recognizable with their curly braces inside.
+ var a = $('a', cells[2]);
if ( request.url.search('{') < 0 ) {
a.attr('href', request.url);
a.css('display', '');
} else {
a.css('display', 'none');
}
- $(cells[3]).text(request.url);
+
+ // reason of why block, if available
+ text = request.block;
+ if ( typeof text === 'string' ) {
+ $(cells[3]).text('\uf0b0');
+ $(cells[3]).attr('data-tip', text);
+ } else {
+ $(cells[3]).text('\u00a0');
+ $(cells[3]).removeAttr('data-tip');
+ }
+
+ // request URL
+ $(cells[4]).text(request.url);
}
/*----------------------------------------------------------------------------*/
function renderRequests() {
- var table = $('#requestsTable tbody');
+ var table = $('#requestsTable');
var requests = updateRequestData();
- var row;
- var rowTemplate = $('#requestRowTemplate', table);
+ var i, row;
+ var rowTemplate = table.find('#requestRowTemplate').first();
// Reuse whatever rows is already in there.
- // Remember: order of elements returned by prevAll() is closest to farthest.
- var rows = $(rowTemplate).prevAll().toArray();
- var i = 0;
- while ( i < requests.length && rows.length ) {
- renderRequestRow(rows.pop(), requests[i]);
- i++;
+ var rows = table.find('.rendered').toArray();
+ for ( i = 0; i < requests.length && rows.length; i++ ) {
+ renderRequestRow(rows[i], requests[i]);
}
+ rows = rows.slice(i);
// Create new rows to receive what is left
- if ( i < requests.length ) {
- do {
- row = rowTemplate.clone();
- renderRequestRow(row, requests[i]);
- row.insertBefore(rowTemplate);
- i++;
- } while ( i < requests.length );
+ for ( ; i < requests.length; i++ ) {
+ row = rowTemplate.clone();
+ renderRequestRow(row, requests[i]);
+ row.insertBefore(rowTemplate);
}
+
// Remove extra rows
- else if ( rows.length ) {
- $(rows).remove();
- }
+ $(rows).remove();
syncWithFilters();
}
diff --git a/js/lists.js b/js/lists.js
index f0ec5fc..6432198 100644
--- a/js/lists.js
+++ b/js/lists.js
@@ -169,6 +169,28 @@ PermissionList.prototype.add = function(other) {
}
};
+/******************************************************************************/
+
+// How much this list differs from the other
+
+PermissionList.prototype.diff = function(other) {
+ var count = 0;
+ // In this one but not the other
+ // In both but different
+ for ( var kthis in this.list ) {
+ if ( this.list.hasOwnProperty(kthis) && this.list[kthis] && !other.list[kthis] ) {
+ count++;
+ }
+ }
+ // In the other but not this one
+ for ( var kother in other.list ) {
+ if ( other.list.hasOwnProperty(kother) && other.list[kother] && !this.list[kother] ) {
+ count++;
+ }
+ }
+ return count;
+};
+
/******************************************************************************/
/******************************************************************************/
@@ -178,11 +200,9 @@ PermissionScope.prototype.toString = function() {
var bin = {
whiteStr: this.white.toString(),
blackStr: this.black.toString(),
- grayStr: this.gray.toString()
+ grayStr: this.gray.toString(),
+ abpFiltering: this.abpFiltering
};
- if ( bin.whiteStr === '' && bin.blackStr === '' && bin.grayStr === '' ) {
- return '';
- }
return JSON.stringify(bin);
};
@@ -191,6 +211,7 @@ PermissionScope.prototype.fromString = function(s) {
this.white.fromString(bin.whiteStr);
this.black.fromString(bin.blackStr);
this.gray.fromString(bin.grayStr);
+ this.abpFiltering = bin.abpFiltering !== undefined ? bin.abpFiltering : true;
};
/******************************************************************************/
@@ -200,6 +221,7 @@ PermissionScope.prototype.assign = function(other) {
this.black.assign(other.black);
this.gray.assign(other.gray);
this.off = other.off;
+ this.abpFiltering = other.abpFiltering;
};
/******************************************************************************/
@@ -212,6 +234,19 @@ PermissionScope.prototype.add = function(other) {
/******************************************************************************/
+PermissionScope.prototype.diff = function(other) {
+ var count =
+ this.white.diff(other.white) +
+ this.black.diff(other.black) +
+ this.gray.diff(other.gray);
+ if ( !this.abpFiltering !== !other.abpFiltering ) {
+ count += 1;
+ }
+ return count;
+};
+
+/******************************************************************************/
+
// This is the heart of HTTP Switchboard:
//
// Check whether something is white or blacklisted, direct or indirectly.
@@ -400,16 +435,16 @@ PermissionScope.prototype.evaluate = function(type, hostname) {
/******************************************************************************/
-PermissionScope.prototype.addRule = function(list, type, hostname) {
- var list = this[list];
+PermissionScope.prototype.addRule = function(listKey, type, hostname) {
+ var list = this[listKey];
if ( !list ) {
throw new Error('PermissionScope.addRule() > invalid list name');
}
return list.addOne(type + '|' + hostname);
};
-PermissionScope.prototype.removeRule = function(list, type, hostname) {
- var list = this[list];
+PermissionScope.prototype.removeRule = function(listKey, type, hostname) {
+ var list = this[listKey];
if ( !list ) {
throw new Error('PermissionScope.removeRule() > invalid list name');
}
@@ -506,7 +541,7 @@ PermissionScopes.prototype.fromString = function(s) {
// duplicates if any.
// https://github.com/gorhill/httpswitchboard/issues/165
// TODO: Remove once all users are beyond v0.7.9.0
- var oldScopeKey, newScopeKey;
+ var newScopeKey;
for ( var oldScopeKey in this.scopes ) {
if ( !this.scopes.hasOwnProperty(oldScopeKey) ) {
continue;
@@ -643,12 +678,36 @@ PermissionScopes.prototype.graylist = function(scopeKey, type, hostname) {
/******************************************************************************/
+PermissionScopes.prototype.getABPFiltering = function(scopeKey) {
+ var scope = this.scopeFromScopeKey(scopeKey);
+ if ( scope ) {
+ return scope.abpFiltering;
+ }
+ return undefined;
+};
+
+PermissionScopes.prototype.toggleABPFiltering = function(scopeKey, state) {
+ var scope = this.scopeFromScopeKey(scopeKey);
+ if ( !scope ) {
+ return undefined;
+ }
+ if ( state === undefined ) {
+ scope.abpFiltering = !scope.abpFiltering;
+ } else {
+ scope.abpFiltering = !!state;
+ }
+ return scope.abpFiltering;
+};
+
+/******************************************************************************/
+
PermissionScopes.prototype.applyRuleset = function(scopeKey, rules) {
var rule, i;
var changed = false;
var scope = this.scopeFromScopeKey(scopeKey);
if ( !scope ) {
- throw new Error('PermissionScopes.applyRuleset() > scope not found');
+ console.error('HTTP Switchboard> PermissionScopes.applyRuleset(): scope not found');
+ return false;
}
i = rules.white.length;
while ( i-- ) {
@@ -665,5 +724,10 @@ PermissionScopes.prototype.applyRuleset = function(scopeKey, rules) {
rule = rules.gray[i];
changed = scope.graylist(rule.type, rule.hostname) || changed;
}
+ if ( rules.abpFiltering !== scope.abpFiltering ) {
+ scope.abpFiltering = !!rules.abpFiltering;
+ changed = true;
+ }
return changed;
};
+
diff --git a/js/pagestats.js b/js/pagestats.js
index 22f1d88..166369f 100644
--- a/js/pagestats.js
+++ b/js/pagestats.js
@@ -21,8 +21,6 @@
/******************************************************************************/
-/******************************************************************************/
-
(function() {
/******************************************************************************/
@@ -166,7 +164,7 @@ var LogEntry = function() {
this.url = '';
this.type = '';
this.when = 0;
- this.blocked = false;
+ this.block = false;
};
var logEntryJunkyard = [];
@@ -330,7 +328,7 @@ PageRequestStats.prototype.logRequest = function(url, type, block) {
logEntry.url = url;
logEntry.type = type;
logEntry.when = Date.now();
- logEntry.blocked = block;
+ logEntry.block = block;
this.ringBufferPointer = ((pointer + 1) % len) | 0;
};
diff --git a/js/popup.js b/js/popup.js
index e5a0253..f9f9e2e 100644
--- a/js/popup.js
+++ b/js/popup.js
@@ -208,7 +208,6 @@ var HTTPSBPopup = {
pageHostname: '',
pageDomain: '',
scopeKey: '*',
-
matrixDomains: {},
@@ -614,6 +613,7 @@ function handleFilter(button, leaning) {
updateMatrixStats();
updateMatrixColors();
updateMatrixBehavior();
+ updateMatrixButtons();
}
function handleWhitelistFilter(button) {
@@ -627,84 +627,60 @@ function handleBlacklistFilter(button) {
/******************************************************************************/
function getTemporaryRuleset() {
- var cells, i, cell;
+ var httpsb = getHTTPSB();
+ var tScopeKey = httpsb.temporaryScopeKeyFromPageURL(HTTPSBPopup.pageURL);
+ var pScopeKey = httpsb.permanentScopeKeyFromPageURL(HTTPSBPopup.pageURL);
var rules = {
- white: null,
- black: null,
- gray: null
+ tScopeKey: tScopeKey,
+ pScopeKey: pScopeKey,
+ white: [],
+ black: [],
+ gray: [],
+ abpFiltering: httpsb.getTemporaryABPFiltering(tScopeKey),
+ count: 0
};
- cells = $('.matRow.rw:not(.meta) .matCell.gdt');
- i = cells.length;
- rules.white = new Array(i);
- while ( i-- ) {
- cell = $(cells[i]);
- rules.white[i] = {
- hostname: cell.prop('hostname'),
- type: cell.prop('reqType')
- };
- }
- cells = $('.matRow.rw:not(.meta) .matCell.rdt');
- i = cells.length;
- rules.black = new Array(i);
- while ( i-- ) {
- cell = $(cells[i]);
- rules.black[i] = {
- hostname: cell.prop('hostname'),
- type: cell.prop('reqType')
- };
- }
- cells = $('.matRow.rw:not(.meta) .matCell.gpt, .matRow.rw:not(.meta) .matCell.rpt');
- i = cells.length;
- rules.gray = new Array(i);
- while ( i-- ) {
- cell = $(cells[i]);
- rules.gray[i] = {
- hostname: cell.prop('hostname'),
- type: cell.prop('reqType')
- };
+ var typeStats, type, typeStat;
+ var tcolor, pcolor;
+ var matrixStats = HTTPSBPopup.matrixStats;
+ for ( var hostname in matrixStats ) {
+ if ( !matrixStats.hasOwnProperty(hostname) ) {
+ continue;
+ }
+ typeStats = matrixStats[hostname].types;
+ for ( type in typeStats ) {
+ if ( !typeStats.hasOwnProperty(type) ) {
+ continue;
+ }
+ typeStat = typeStats[type];
+ tcolor = typeStat.temporaryColor.slice(0, 2);
+ pcolor = typeStat.permanentColor.slice(0, 2);
+ if ( tcolor === pcolor ) {
+ continue;
+ }
+ if ( tcolor === 'gd' ) {
+ rules.white.push({ hostname: hostname, type: type });
+ } else if ( tcolor === 'rd' ) {
+ rules.black.push({ hostname: hostname, type: type });
+ } else if ( pcolor !== 'xx' ) {
+ rules.gray.push({ hostname: hostname, type: type });
+ }
+ }
}
- return rules;
-}
-
-/******************************************************************************/
-
-// Handle user interaction with persistence buttons
-
-function persistScope() {
- var httpsb = getHTTPSB();
- var scopeKey = httpsb.temporaryScopeKeyFromPageURL(HTTPSBPopup.pageURL);
- if ( httpsb.isGlobalScopeKey(scopeKey) ) {
- httpsb.createPermanentGlobalScope(HTTPSBPopup.pageURL);
- } else if ( httpsb.isDomainScopeKey(scopeKey) ) {
- httpsb.createPermanentDomainScope(HTTPSBPopup.pageURL);
- } else if ( httpsb.isSiteScopeKey(scopeKey) ) {
- httpsb.createPermanentSiteScope(HTTPSBPopup.pageURL);
+ rules.count += rules.white.length + rules.black.length + rules.gray.length;
+ if ( rules.abpFiltering !== httpsb.getPermanentABPFiltering(pScopeKey) ) {
+ rules.count += 1;
}
- httpsb.applyRulesetPermanently(scopeKey, getTemporaryRuleset());
- updateMatrixStats();
- updateScopeCell();
- updateMatrixColors();
- updateMatrixBehavior();
-}
-
-/******************************************************************************/
-
-function revertScope() {
- var httpsb = getHTTPSB();
- var scopeKey = httpsb.temporaryScopeKeyFromPageURL(HTTPSBPopup.pageURL);
- httpsb.revertScopeRules(scopeKey);
- updateScopeCell();
- updateMatrixStats();
- updateMatrixColors();
- updateMatrixBehavior();
-}
+ // A temporary scope different from the permanent scope counts for one.
+ if ( tScopeKey !== pScopeKey ) {
+ rules.count += 1;
+ }
+ // If temporary scope is different than permanent scope, all the rules in
+ // the permanent scope of narrower level would cease to exist, so we need
+ // to count them as well.
+ // TODO: Undecided whether this should be accounted for, as they are not
+ // seen by the user.
-function revertAll() {
- getHTTPSB().revertAllRules();
- updateScopeCell();
- updateMatrixStats();
- updateMatrixColors();
- updateMatrixBehavior();
+ return rules;
}
/******************************************************************************/
@@ -1115,6 +1091,10 @@ function makeMenu() {
updateMatrixBehavior();
HTTPSBPopup.matrixList.appendTo($('.paneContent'));
+
+ initScopeCell();
+ updateMatrixButtons();
+ populatePresets();
}
/******************************************************************************/
@@ -1146,9 +1126,9 @@ function createGlobalScope() {
var httpsb = getHTTPSB();
httpsb.createTemporaryGlobalScope(HTTPSBPopup.pageURL);
updateMatrixStats();
- updateScopeCell();
updateMatrixColors();
updateMatrixBehavior();
+ updateMatrixButtons();
dropDownMenuHide();
}
@@ -1156,9 +1136,9 @@ function createDomainScope() {
var httpsb = getHTTPSB();
httpsb.createTemporaryDomainScope(HTTPSBPopup.pageURL);
updateMatrixStats();
- updateScopeCell();
updateMatrixColors();
updateMatrixBehavior();
+ updateMatrixButtons();
dropDownMenuHide();
}
@@ -1166,9 +1146,9 @@ function createSiteScope() {
var httpsb = getHTTPSB();
httpsb.createTemporarySiteScope(HTTPSBPopup.pageURL);
updateMatrixStats();
- updateScopeCell();
updateMatrixColors();
updateMatrixBehavior();
+ updateMatrixButtons();
dropDownMenuHide();
}
@@ -1216,6 +1196,84 @@ function updateScopeCell() {
/******************************************************************************/
+function updateABPbutton() {
+ var httpsb = getHTTPSB();
+ var pageStats = getPageStats();
+ var count = pageStats ? pageStats.abpBlockCount : '';
+ var scopeKey = httpsb.temporaryScopeKeyFromPageURL(HTTPSBPopup.pageURL);
+ var button = $('#buttonABPFiltering');
+ button.toggleClass('disabled', !httpsb.getTemporaryABPFiltering(scopeKey));
+ button.children('span.badge').text(count);
+ button.attr('data-tip', button.data('tip').replace('{{abpCount}}', count));
+}
+
+function toggleABPFiltering() {
+ var httpsb = getHTTPSB();
+ var scopeKey = httpsb.temporaryScopeKeyFromPageURL(HTTPSBPopup.pageURL);
+ httpsb.toggleTemporaryABPFiltering(scopeKey);
+ updateMatrixButtons();
+}
+
+/******************************************************************************/
+
+function updatePersistButton() {
+ var ruleset = getTemporaryRuleset();
+
+ var button = $('#buttonPersist');
+ button.contents()
+ .filter(function(){return this.nodeType===3;})
+ .first()[0]
+ .textContent = ruleset.count > 0 ? '\uf13e' : '\uf023';
+ button.children('span.badge').text(ruleset.count > 0 ? ruleset.count : '');
+ var disabled = ruleset.count === 0;
+ button.toggleClass('disabled', disabled);
+
+ $('#buttonRevertScope').toggleClass('disabled', disabled);
+
+}
+
+function persistScope() {
+ var httpsb = getHTTPSB();
+ var rulset = getTemporaryRuleset();
+ if ( httpsb.isGlobalScopeKey(rulset.tScopeKey) ) {
+ httpsb.createPermanentGlobalScope(HTTPSBPopup.pageURL);
+ } else if ( httpsb.isDomainScopeKey(rulset.tScopeKey) ) {
+ httpsb.createPermanentDomainScope(HTTPSBPopup.pageURL);
+ } else if ( httpsb.isSiteScopeKey(rulset.tScopeKey) ) {
+ httpsb.createPermanentSiteScope(HTTPSBPopup.pageURL);
+ }
+ httpsb.applyRulesetPermanently(rulset.tScopeKey, rulset);
+ updateMatrixStats();
+ updateMatrixColors();
+ updateMatrixBehavior();
+ updateMatrixButtons();
+}
+
+/******************************************************************************/
+
+// rhill 2014-03-12: revert completely ALL changes related to the
+// current page, including scopes.
+
+function revertScope() {
+ var httpsb = getHTTPSB();
+ var rulset = getTemporaryRuleset();
+ httpsb.revertScopeRules(rulset.tScopeKey);
+ if ( httpsb.isGlobalScopeKey(rulset.pScopeKey) ) {
+ httpsb.createTemporaryGlobalScope(HTTPSBPopup.pageURL);
+ } else if ( httpsb.isDomainScopeKey(rulset.pScopeKey) ) {
+ httpsb.createTemporaryDomainScope(HTTPSBPopup.pageURL);
+ } else if ( httpsb.isSiteScopeKey(rulset.pScopeKey) ) {
+ httpsb.createTemporarySiteScope(HTTPSBPopup.pageURL);
+ }
+ httpsb.revertScopeRules(rulset.pScopeKey);
+ updateMatrixStats();
+ updateMatrixColors();
+ updateMatrixBehavior();
+ updateMatrixButtons();
+}
+
+/******************************************************************************/
+
// Offer a list of presets relevant to the current matrix
function populatePresets() {
@@ -1265,10 +1323,10 @@ function populatePresets() {
// Button
// https://github.com/gorhill/httpswitchboard/issues/174
- $('#buttonPresets').attr('disabled', !presets.length);
+ $('#buttonPresets').toggleClass('disabled', !presets.length);
// Button badge
- $('#buttonPresets > span').text(presets.length);
+ $('#buttonPresets > span.badge').text(presets.length);
}
function presetEntryHandler() {
@@ -1277,10 +1335,30 @@ function presetEntryHandler() {
httpsb.temporaryScopeKeyFromPageURL(HTTPSBPopup.pageURL),
$(this).prop('presetId')
);
+ updateMatrixStats();
+ updateMatrixColors();
+ updateMatrixBehavior();
+ updateMatrixButtons();
+}
+
+/******************************************************************************/
+
+// Buttons which are affected by any changes in the matrix
+
+function updateMatrixButtons() {
updateScopeCell();
+ updateABPbutton();
+ updatePersistButton();
+}
+
+/******************************************************************************/
+
+function revertAll() {
+ getHTTPSB().revertAllRules();
updateMatrixStats();
updateMatrixColors();
updateMatrixBehavior();
+ updateMatrixButtons();
}
/******************************************************************************/
@@ -1342,27 +1420,17 @@ function bindToTabHandler(tabs) {
// Now that tabId and pageURL are set, we can build our menu
initMenuEnvironment();
makeMenu();
- populatePresets();
// After popup menu is built, check whether there is a non-empty matrix
if ( !HTTPSBPopup.matrixHasRows ) {
- $('#no-traffic').css('display', '');
$('#matHead').remove();
- $('#toolbarScope').remove();
- $('#buttonPersist').remove();
- $('#buttonPresets').remove();
- $('#buttonReload').remove();
- $('#buttonRevertScope').remove();
- $('#buttonRevertAll').remove();
+ $('#toolbarLeft').remove();
// https://github.com/gorhill/httpswitchboard/issues/191
$('#noNetTrafficPrompt').text(chrome.i18n.getMessage('matrixNoNetTrafficPrompt'));
$('#noNetTrafficPrompt').css('display', '');
}
- // Activate page scope if there is one
- initScopeCell();
-
// To know when to rebuild the matrix
// TODO: What if this event is triggered before bindToTabHandler()
// is called?
@@ -1427,9 +1495,9 @@ function dropDownMenuHide() {
/******************************************************************************/
-// make menu only when popup html is fully loaded
+// Make menu only when popup html is fully loaded
-function initAll() {
+$(function() {
chrome.tabs.query({currentWindow: true, active: true}, bindToTabHandler);
// Below is UI stuff which is not key to make the menu, so this can
@@ -1463,12 +1531,13 @@ function initAll() {
$('#scopeKeyGlobal').on('click', createGlobalScope);
$('#scopeKeyDomain').on('click', createDomainScope);
$('#scopeKeySite').on('click', createSiteScope);
+ $('#buttonABPFiltering').on('click', toggleABPFiltering);
$('#buttonPersist').on('click', persistScope);
$('#buttonRevertScope').on('click', revertScope);
- $('#buttonRevertAll').on('click', revertAll);
$('body').on('click', '.presetEntry', presetEntryHandler);
+ $('#buttonRevertAll').on('click', revertAll);
$('#buttonReload').on('click', buttonReloadHandler);
$('.extensionURL').on('click', gotoExtensionURL);
$('.externalURL').on('click', gotoExternalURL);
@@ -1486,14 +1555,13 @@ function initAll() {
value: separator.hasClass('g3Collapsed')
});
});
-}
-/******************************************************************************/
-
-// Entry point
-
-$(function(){
- initAll();
+ // Tool tips
+ $('[data-i18n-tip]').each(function() {
+ var me = $(this);
+ var key = me.data('i18nTip');
+ me.attr('data-tip', chrome.i18n.getMessage(key));
+ });
});
/******************************************************************************/
diff --git a/js/reqstats.js b/js/reqstats.js
index 84a80ad..df3afb7 100644
--- a/js/reqstats.js
+++ b/js/reqstats.js
@@ -37,7 +37,7 @@ _WebRequestStats.prototype.reset = function() {
/******************************************************************************/
WebRequestStats.prototype.record = function(type, blocked) {
- if ( blocked ) {
+ if ( blocked !== false ) {
this.blocked[type] += 1;
this.blocked.all += 1;
} else {
diff --git a/js/tab.js b/js/tab.js
index 267012d..a47a923 100644
--- a/js/tab.js
+++ b/js/tab.js
@@ -47,6 +47,7 @@ PageStatsEntry.prototype.init = function(pageUrl) {
this.perLoadAllowedRequestCount = 0;
this.perLoadBlockedRequestCount = 0;
this.ignore = false;
+ this.abpBlockCount = 0;
return this;
};
@@ -70,6 +71,9 @@ PageStatsEntry.prototype.dispose = function() {
/******************************************************************************/
+// rhill 2014-03-11: If `block` !== false, then block.toString() may return
+// user legible information about the reason for the block.
+
PageStatsEntry.prototype.recordRequest = function(type, url, block) {
// TODO: this makes no sense, I forgot why I put this here.
if ( !this ) {
@@ -85,7 +89,7 @@ PageStatsEntry.prototype.recordRequest = function(type, url, block) {
// Count blocked/allowed requests
this.requestStats.record(type, block);
- if ( block ) {
+ if ( block !== false ) {
this.perLoadBlockedRequestCount++;
} else {
this.perLoadAllowedRequestCount++;
@@ -107,7 +111,7 @@ PageStatsEntry.prototype.recordRequest = function(type, url, block) {
// result in unnecessary reloads (because requests can be made *after*
// the page load has completed).
// https://github.com/gorhill/httpswitchboard/issues/98
- if ( block ) {
+ if ( block !== false ) {
this.state[type + '|' + hostname] = true;
}
diff --git a/js/traffic.js b/js/traffic.js
index 9335902..492f797 100644
--- a/js/traffic.js
+++ b/js/traffic.js
@@ -291,14 +291,17 @@ var onBeforeRequestHandler = function(details) {
}
// Block request?
- // https://github.com/gorhill/httpswitchboard/issues/27
- var block = httpsb.blacklisted(pageURL, type, requestHostname);
+ var scopeKey = httpsb.temporaryScopeKeyFromPageURL(pageURL);
+ var scope = httpsb.temporaryScopeFromScopeKey(scopeKey);
+ var block = scope.evaluate(type, requestHostname).charAt(0) === 'r';
// Block using ABP filters?
- if ( !block ) {
+ if ( block === false && scope.abpFiltering === true ) {
block = httpsb.abpFilters.matchString(requestURL);
- if ( block ) {
- httpsb.abpHitCount += 1;
+ if ( block !== false ) {
+ pageStats.abpBlockCount += 1;
+ httpsb.abpBlockCount += 1;
+ block = 'ABP filter: ' + block;
}
}
diff --git a/js/types.js b/js/types.js
index f9f67e3..ba0ba00 100644
--- a/js/types.js
+++ b/js/types.js
@@ -52,6 +52,7 @@ function PermissionScope() {
this.white = new PermissionList();
this.black = new PermissionList(['*|*']);
this.gray = new PermissionList();
+ this.abpFiltering = true;
}
function PermissionScopes() {
@@ -75,6 +76,7 @@ function PageStatsEntry(pageUrl) {
this.perLoadAllowedRequestCount = 0;
this.perLoadBlockedRequestCount = 0;
this.off = false;
+ this.abpBlockCount = 0;
this.init(pageUrl);
}
diff --git a/popup.html b/popup.html
index 047ed9b..ad94629 100644
--- a/popup.html
+++ b/popup.html
@@ -18,14 +18,7 @@