From 16435547718f1e334d46c20a2930c533881f0341 Mon Sep 17 00:00:00 2001 From: gorhill Date: Wed, 12 Mar 2014 10:56:37 -0400 Subject: [PATCH] Fixes #208, #205, #189, and other changes related to better integrate ABP filters --- _locales/de/messages.json | 24 ++++ _locales/en/messages.json | 24 ++++ _locales/fr/messages.json | 24 ++++ css/popup.css | 150 +++++++++++++-------- info.html | 31 ++++- js/abp-filters.js | 40 ++++-- js/background.js | 2 +- js/cookies.js | 8 +- js/httpsb.js | 39 ++++++ js/info.js | 77 +++++++---- js/lists.js | 84 ++++++++++-- js/pagestats.js | 6 +- js/popup.js | 268 ++++++++++++++++++++++++-------------- js/reqstats.js | 2 +- js/tab.js | 8 +- js/traffic.js | 13 +- js/types.js | 2 + popup.html | 32 ++--- 18 files changed, 587 insertions(+), 247 deletions(-) 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>
    whenwhatwhere
    <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 @@
    - -
    - - - -
    - -
    +
    -
    + + + +
    -
    - - + +
    + - - - - + + + +
    @@ -54,9 +50,7 @@
    -
    - -
    +