diff --git a/README.md b/README.md
index fb85c0e..80cb5d4 100644
--- a/README.md
+++ b/README.md
@@ -47,9 +47,9 @@ HTTP Switchboard (FOSS) put you in FULL control of where your browser is allowed
You can blacklist/whitelist a single cell, an entire row, a group of rows, an entire column, or the whole matrix with just one click.
-HTTP Switchboard matrix uses precedence logic to evaluate what is blocked/allowed according to which cells are blacklisted/whitelisted. For example, this allows the user to whitelist a whole page with one click, without having to repeatedly whitelist whatever new data appear on the page.
+HTTP Switchboard matrix uses precedence logic to evaluate what is blocked/allowed according to which cells are blacklisted/whitelisted. For example, this allows you to whitelist a whole page with one click, without having to repeatedly whitelist whatever new data appear on the page.
-You can also create scopes for your whitelist/blacklist rules. For example, this allow you to whitelist `facebook.com` ONLY when visiting Facebook web site.
+You can also create scopes for your whitelist/blacklist rules. For example, this allows you to whitelist `facebook.com` ONLY when visiting Facebook web site.
The goal of this extension is to make the allowing or blocking of web sites, wholly or partly, as straightforward as possible, so as to not discourage those users who give up easily on good security and privacy habits.
diff --git a/css/fonts.css b/css/fonts.css
index a6ef215..4b52aee 100644
--- a/css/fonts.css
+++ b/css/fonts.css
@@ -34,4 +34,12 @@
font-weight: normal;
font-style: normal;
}
+.fa {
+ font-family: FontAwesome;
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+ vertical-align: baseline;
+ display: inline-block;
+ }
diff --git a/css/popup.css b/css/popup.css
index 4c4ea8b..49dafae 100644
--- a/css/popup.css
+++ b/css/popup.css
@@ -1,12 +1,3 @@
-.fa {
- font-family: FontAwesome;
- font-style: normal;
- font-weight: normal;
- line-height: 1;
- vertical-align: baseline;
- display: inline-block;
- }
-
body {
margin: 0;
border: 0;
@@ -16,53 +7,117 @@ body {
min-width: 32em;
min-height: 15em;
}
+*:focus {
+ outline: none;
+ }
.paneHead {
padding: 2px;
position: fixed;
top: 0;
+ height: 5.5em;
left:0;
right: 0;
background-color: white;
z-index: 10;
}
+.paneContent {
+ padding-top: 5.5em;
+ }
-#toolbar {
- padding-bottom:1.5em;
+.toolbar {
+ padding-bottom: 0.5em;
+ display: inline-block;
+ vertical-align: top;
}
-#toolbar button {
+.toolbar button {
margin: 0;
border: 0;
padding: 4px;
- font-size: 1.75em;
- min-width: 1.3em;
- opacity: 0.7;
+ font: inherit;
+ background-color: white;
+ opacity: 0.9;
+ cursor: pointer;
}
-#toolbar button:hover {
+.toolbar button:hover {
+ background-color: #eee;
opacity: 1;
}
-.btn-group.pull-right > .btn {
- float: left;
+.toolbar button.fa {
+ font: 1.75em 'FontAwesome';
+ min-width: 1.3em;
}
-body #scopePersist {
+.pullright {
+ float: right;
+ }
+
+.dropdown-menu {
+ margin: 0;
+ border: 0;
+ padding: 3px 0 0 0;
+ position: absolute;
+ z-index: 20;
+ font-size: 110%;
+ display: none;
+ }
+.dropdown-menu.pullright {
+ right: 0;
+ }
+.dropdown-menu > ul {
+ margin: 0;
+ border: 0;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ padding: 3px 0;
+ background-color: white;
+ list-style-type: none;
+ }
+.dropdown-menu > ul > li.dropdown-menu-entry {
+ margin: 0;
+ border: 0;
+ padding: 4px 0.5em;
+ color: black;
+ cursor: pointer;
+ }
+.dropdown-menu > ul > li.dropdown-menu-entry:hover {
+ background: #eee;
+ }
+.dropdown-menu > ul > li.dropdown-menu-entry-divider {
+ margin: 0.5em 0;
+ border-top: 1px solid #ccc;
+ }
+.dropdown-menu > ul > li.dropdown-menu-entry > .fa {
+ margin-right: 0.5em;
+ font-size: 110%;
+ color: #aaa;
+ }
+.dropdown-menu-button ~ .dropdown-menu {
+ display: none;
+ }
+.dropdown-menu-button:focus ~ .dropdown-menu {
+ display: block;
+ }
+
+body #buttonRevert {
margin-right: 1em;
}
-body.tScopeGlobal #scopePersist {
+body.tScopeGlobal #buttonPersist {
color: #000;
}
-body.tScopeDomain #scopePersist {
+body.tScopeDomain #buttonPersist {
color: #24c;
}
-body.tScopeSite #scopePersist {
+body.tScopeSite #buttonPersist {
color: #48c;
}
-body.powerOff #scopePersist {
+body.powerOff #buttonPersist {
visibility: hidden;
}
body.powerOff #buttonRevert {
visibility: hidden;
}
+
.dropdown-menu > li > a > i {
padding: 0 6px;
font-size: 1.2em;
@@ -74,23 +129,25 @@ body.powerOff #buttonPower i {
color: #c00;
}
-body #scopeMenu {
- margin: 0 0 1em 0;
+body #toolbarScope {
+ margin: 0;
border: 0;
padding: 0;
display: inline-block;
box-sizing: content-box;
position: relative;
}
-body.powerOff #scopeMenu {
+body.powerOff #toolbarScope {
visibility: hidden;
}
-body #scopeCell {
+
+body .toolbar button#scopeCell {
margin: 0;
border: 1px dotted rgba(0,0,0,0.2);
padding: 6px 1px 3px 1px;
box-sizing: content-box;
width: 18em;
+ height: 1.5em;
white-space: nowrap;
text-align: right;
line-height: 100%;
@@ -116,43 +173,14 @@ body.pScopeDomain #scopeCell {
body.pScopeSite #scopeCell {
background-image: url('/img/permanent-scope-site.png');
}
+
body #scopeKeys {
- position: absolute;
- z-index: 20;
- display: none;
- }
-body #scopeKeys > div {
- margin: 0;
- border: 0;
- padding-top: 3px;
- }
-body #scopeKeys > div > div {
- margin: 0;
- border: 0;
- border: 1px solid #ccc;
- border-radius: 4px;
- padding: 3px 0;
- background-color: white;
- }
-body #scopeKeys > div > div > div {
- margin: 0;
- border: 0;
padding: 6px 1px 3px 1px;
- box-sizing: content-box;
- width: 18em;
+ left: 0;
+ right: 0;
text-align: right;
- cursor: pointer;
- }
-body #scopeKeys > div > div > div:hover {
- background: #eee;
- }
-body #scopeCell:focus ~ #scopeKeys {
- display: block;
}
-.paneContent {
- padding-top: 5em;
- }
.matrix {
text-align: left;
}
@@ -171,6 +199,11 @@ body #scopeCell:focus ~ #scopeKeys {
line-height: 100%;
position: relative;
}
+
+.paneHead #matHead {
+ position: absolute;
+ bottom: 3px;
+ }
.paneHead .matCell:nth-child(2) {
letter-spacing: -0.3px;
}
@@ -233,7 +266,7 @@ body #scopeCell:focus ~ #scopeKeys {
margin: 0;
padding: 0;
border: 0;
- height: 9px;
+ height: 3px;
background: url('/img/matrix-group-hide.png') no-repeat center top 0,
url('/img/matrix-group-hline.png') repeat-x center top 3px;
opacity: 0.2;
diff --git a/css/rulemanager.css b/css/rulemanager.css
new file mode 100644
index 0000000..0d821f8
--- /dev/null
+++ b/css/rulemanager.css
@@ -0,0 +1,245 @@
+body {
+ margin: 0;
+ padding: 0;
+ font: 15px httpsb,sans-serif;
+ width: 100%;
+ }
+h1:first-child {
+ margin: 0 0 1em 0;
+ }
+#navi-bar {
+ border-bottom: 1px solid #ccc;
+ padding: 1em;
+ position: fixed;
+ background-color: white;
+ width: 100%;
+ height: 8em;
+ z-index: 100;
+ }
+#navi-bar + div {
+ padding: 10em 1em 1em 1em;
+ }
+h2 {
+ margin: 0.5em 0;
+ padding: 0.5em 0;
+ }
+h2 + *, h1 + * {
+ margin: 0 1em;
+ }
+table {
+ border: 0;
+ border-collapse: collapse;
+ }
+table td {
+ margin: 0;
+ border: 0;
+ padding: 4px 0;
+ vertical-align: top;
+ }
+table td h2 {
+ margin: 0 0 0.75em 0;
+ }
+textarea {
+ box-sizing: border-box;
+ width: 99%;
+ height: 20em;
+ }
+button {
+ margin: 0 1em 0 0;
+ border: 1px solid rgba(0,0,0,0.2);
+ border-radius: 5px;
+ padding: 0.5em 1em;
+ font: inherit;
+ font-size: 110%;
+ background-color: #eee;
+ cursor: pointer;
+ }
+button .fa {
+ margin-right: 0.5em;
+ font-size: larger;
+ }
+
+#recipeDecode, #recipeImport, #recipeEncode, #recipeExport {
+ cursor: pointer;
+ opacity: 0.5;
+ }
+#recipeImport, #recipeExport {
+ padding-top: 32px;
+ display: inline-block;
+ }
+#recipeDecode, #recipeEncode {
+ margin: 0 4px;
+ padding-top: 28px;
+ display: inline-block;
+ height: 28px;
+ }
+#recipeDecode {
+ margin-bottom: 1em;
+ background: url('/img/decode.png') no-repeat center top;
+ }
+#recipeEncode {
+ margin-top: 1em;
+ background: url('/img/encode.png') no-repeat center top;
+ }
+#recipeImport {
+ background: url('/img/import.png') no-repeat center top;
+ }
+#recipeExport {
+ background: url('/img/export.png') no-repeat center top;
+ }
+#recipeDecode:hover, #recipeImport:hover, #recipeEncode:hover, #recipeExport:hover {
+ opacity: 1;
+ }
+.recipe {
+ margin: 2px;
+ border: 1px solid #ddd;
+ padding: 1px;
+ font-family: monospace;
+ font-size: smaller;
+ line-height: 110%;
+ color: #888;
+ background-color: #f2f2f2;
+ white-space: pre;
+ overflow-x: hidden;
+ }
+.scopes ul {
+ margin: 0;
+ padding-left: 1em;
+ list-style: none;
+ }
+.scopes > ul {
+ padding: 0;
+ }
+.scopes > ul > li.scope {
+ margin: 4px;
+ border: 1px dotted #ccc;
+ display: inline-block;
+ vertical-align: top;
+ }
+.scopes > ul > li.scope:hover {
+ border: 1px solid #ccc;
+ }
+.scopes > ul > li.scope.todelete {
+ background-color: #fee;
+ text-decoration: line-through;
+ }
+.scopes > ul > li.scope > div:first-child {
+ margin: 0;
+ border: 0;
+ padding: 2px;
+ }
+.scopes > ul > li.scope > div:first-child .scopeName {
+ cursor: pointer;
+ }
+.scopes > ul > li.scope > div:first-child .state {
+ padding: 3px 3px 0 3px;
+ float: right;
+ font-size: 120%;
+ }
+.scopes > ul > li.scope > div:first-child .state::before {
+ content: '\f09c';
+ }
+.scopes > ul > li.scope.permanent > div:first-child .state::before {
+ content: '\f023';
+ opacity: 0.25;
+ }
+.scopes > ul > li.scope.todelete > div:first-child .state::before {
+ content: '\f00d';
+ opacity: 1;
+ color: red;
+ }
+#global.scopes > ul > li.scope > div:first-child {
+ color: #000;
+ background-color: #eee;
+ }
+#global.scopes > ul > li.scope > ul > li > ul {
+ min-width: 12em;
+ }
+#perdomain.scopes > ul > li.scope > *:first-child {
+ color: #24c;
+ background-color: #eee;
+ }
+#persite.scopes > ul > li.scope > *:first-child {
+ color: #48c;
+ background-color: #f4f4f4;
+ }
+#global.scopes > ul > li.scope > ul > li {
+ margin-left: 2em;
+ display: inline-block;
+ vertical-align: top;
+ }
+#global.scopes > ul > li.scope > ul > li:first-child {
+ margin-left: 0;
+ }
+.scopes > ul > li.scope > .recipe {
+ margin: 1em 2px 2px 2px;
+ border: 1px solid #ddd;
+ padding: 1px;
+ font-size: 12px;
+ line-height: 110%;
+ display: inline-block;
+ vertical-align: bottom;
+ width: 20em;
+ height: 2em;
+ color: #888;
+ background-color: #f2f2f2;
+ white-space: pre;
+ overflow: hidden;
+ opacity: 0.25;
+ }
+.scopes > ul > li.scope .recipe:hover {
+ opacity: 1.0;
+ }
+.scopes > ul > li.scope > ul > li.white {
+ color: #080;
+ }
+.scopes > ul > li.scope > ul > li.black {
+ color: #c00;
+ }
+.scopes > ul > li.scope > ul > li.gray {
+ color: #aaa;
+ }
+.scopes > ul > li.scope > ul > li > ul > li {
+ cursor: pointer;
+ }
+.scopes > ul > li.scope > ul > li > ul > li:hover {
+ background-color: #eef;
+ }
+.scopes > ul > li.scope > ul > li > ul > li > span.state {
+ padding: 4px 4px 0 4px;
+ display: inline-block;
+ float: right;
+ visibility: hidden;
+ }
+.scopes > ul > li.scope.permanent > ul > li > ul > li > span.state {
+ visibility: visible;
+ }
+.scopes > ul > li.scope.permanent > ul > li > ul > li > span.state::before {
+ content: '\f09c';
+ }
+.scopes > ul > li.scope.permanent > ul > li > ul > li.permanent > span.state::before {
+ content: '\f023';
+ opacity: 0.25;
+ }
+.scopes > ul > li.scope.todelete > ul > li > ul > li.rule {
+ background-color: #fee;
+ text-decoration: line-through;
+ }
+.scopes > ul > li.scope > ul > li > ul > li.rule.todelete {
+ background-color: #fee;
+ text-decoration: line-through;
+ }
+.scopes > ul > li.scope > ul > li > ul > li.rule.todelete > span.state::before {
+ content: '\f00d';
+ opacity: 1;
+ color: red;
+ }
+.scopes > ul > li.scope.todelete > ul > li > ul > li.rule > span.state::before {
+ content: '\f00d';
+ opacity: 1;
+ color: red;
+ }
+.bad {
+ background-color: #fdd;
+ }
+
diff --git a/js/background.js b/js/background.js
index b7641c5..2cf1042 100644
--- a/js/background.js
+++ b/js/background.js
@@ -74,8 +74,6 @@ var HTTPSB = {
temporaryScopes: null,
permanentScopes: null,
- temporaryScopeJunkyard: {},
-
// Current entries from remote blacklists --
// just hostnames, '*/' is implied, this saves significantly on memory.
blacklistReadonly: {},
diff --git a/js/httpsb.js b/js/httpsb.js
index bc1e33a..b47c039 100644
--- a/js/httpsb.js
+++ b/js/httpsb.js
@@ -38,100 +38,6 @@ HTTPSB.normalizeScopeURL = function(url) {
/******************************************************************************/
-HTTPSB.createPageScopeIfNotExists = function(url) {
- if ( url && url === '*' ) {
- return true;
- }
- if ( !url ) {
- return false;
- }
- url = uriTools.rootURLFromURI(url);
- var tscope = this.temporaryScopes.scopes[url];
- var pscope = this.permanentScopes.scopes[url];
- if ( !tscope !== !pscope ) {
- throw 'HTTP Switchboard.createPageScopeIfNotExists(): corrupted internal state';
- }
- // Skip everything if scopes exist and are switched on
- if ( tscope && !tscope.off && pscope && !pscope.off ) {
- return false;
- }
- // Create temporary scope or switch it on
- if ( !tscope ) {
- tscope = new PermissionScope();
- tscope.whitelist('main_frame', '*');
- tscope.whitelist('stylesheet', '*');
- tscope.whitelist('image', '*');
- this.temporaryScopes.scopes[url] = tscope;
- } else {
- tscope.off = false;
- }
- // Create permanent scope or switch it on
- if ( !pscope ) {
- pscope = new PermissionScope();
- pscope.whitelist('main_frame', '*');
- pscope.whitelist('stylesheet', '*');
- pscope.whitelist('image', '*');
- this.permanentScopes.scopes[url] = pscope;
- } else {
- pscope.off = false;
- }
-
- // Page-scoped permissions are always persisted, so that the
- // entry is present, in order to be sure at least '*|main_frame' is
- // persisted.
- this.savePermissions();
-
- return true;
-};
-
-/******************************************************************************/
-
-HTTPSB.destroyPageScopeIfExists = function(url) {
- if ( !url || url === '*' ) {
- return false;
- }
- url = uriTools.rootURLFromURI(url);
- var tscope = this.temporaryScopes.scopes[url];
- var pscope = this.permanentScopes.scopes[url];
- if ( !tscope !== !pscope ) {
- throw 'HTTP Switchboard.destroyPageScopeIfExists(): corrupted internal state';
- }
- if ( !tscope && !pscope ) {
- return false;
- }
- if ( tscope.off && pscope.off ) {
- return false;
- }
- tscope.off = true;
- pscope.off = true;
-
- // Flush out the page permissions from storage.
- this.savePermissions();
-
- return true;
-};
-
-/******************************************************************************/
-
-HTTPSB.scopePageExists = function(url) {
- if ( !url ) {
- return false;
- }
- // Global scope always exists
- if ( url === '*' ) {
- return true;
- }
- url = uriTools.rootURLFromURI(url);
- var tscope = this.temporaryScopes.scopes[url];
- var pscope = this.permanentScopes.scopes[url];
- if ( !tscope !== !pscope ) {
- throw 'HTTP Switchboard.scopePageExists(): corrupted internal state';
- }
- return tscope && !tscope.off && pscope && !pscope.off;
-};
-
-/******************************************************************************/
-
HTTPSB.globalScopeKey = function() {
return '*';
};
@@ -183,14 +89,16 @@ HTTPSB.isValidScopeKey = function(scopeKey) {
HTTPSB.createTemporaryGlobalScope = function(url) {
var scopeKey, scope;
scopeKey = this.siteScopeKeyFromURL(url);
- scope = this.removeTemporaryScope(scopeKey);
- if ( scope ) {
- this.temporaryScopeJunkyard[scopeKey] = scope;
+ this.removeTemporaryScopeFromScopeKey(scopeKey);
+ if ( scopeKey.indexOf('https:') === 0 ) {
+ scopeKey = 'http:' + scopeKey.slice(6);
+ this.removeTemporaryScopeFromScopeKey(scopeKey);
}
scopeKey = this.domainScopeKeyFromURL(url);
- scope = this.removeTemporaryScope(scopeKey);
- if ( scope ) {
- this.temporaryScopeJunkyard[scopeKey] = scope;
+ this.removeTemporaryScopeFromScopeKey(scopeKey);
+ if ( scopeKey.indexOf('https:') === 0 ) {
+ scopeKey = 'http:' + scopeKey.slice(6);
+ this.removeTemporaryScopeFromScopeKey(scopeKey);
}
};
@@ -198,15 +106,25 @@ HTTPSB.createPermanentGlobalScope = function(url) {
var changed = false;
// Remove potentially occulting domain/site scopes.
var scopeKey = this.siteScopeKeyFromURL(url);
- var scope = this.removePermanentScope(scopeKey);
- if ( scope ) {
+ if ( this.removePermanentScopeFromScopeKey(scopeKey) ) {
changed = true;
}
+ if ( scopeKey.indexOf('https:') === 0 ) {
+ scopeKey = 'http:' + scopeKey.slice(6);
+ if ( this.removeTemporaryScopeFromScopeKey(scopeKey) ) {
+ changed = true;
+ }
+ }
scopeKey = this.domainScopeKeyFromURL(url);
- scope = this.removePermanentScope(scopeKey);
- if ( scope ) {
+ if ( this.removePermanentScopeFromScopeKey(scopeKey) ) {
changed = true;
}
+ if ( scopeKey.indexOf('https:') === 0 ) {
+ scopeKey = 'http:' + scopeKey.slice(6);
+ if ( this.removeTemporaryScopeFromScopeKey(scopeKey) ) {
+ changed = true;
+ }
+ }
if ( changed ) {
this.savePermissions();
}
@@ -216,27 +134,23 @@ HTTPSB.createPermanentGlobalScope = function(url) {
/******************************************************************************/
HTTPSB.createTemporaryDomainScope = function(url) {
- var scopeKey, scope;
-
// Already created?
- scopeKey = this.domainScopeKeyFromURL(url);
- if ( !this.temporaryScopes.scopes[scopeKey] ) {
- // See if there is a match in junkyard
- scope = this.temporaryScopeJunkyard[scopeKey];
- if ( !scope ) {
- scope = new PermissionScope();
- scope.whitelist('main_frame', '*');
- } else {
- delete this.temporaryScopeJunkyard[scopeKey];
- }
+ var scopeKey = this.domainScopeKeyFromURL(url);
+ var scope = this.temporaryScopes.scopes[scopeKey];
+ if ( !scope ) {
+ scope = new PermissionScope();
+ scope.whitelist('main_frame', '*');
this.temporaryScopes.scopes[scopeKey] = scope;
+ } else if ( scope.off ) {
+ scope.off = false;
}
// Remove potentially occulting site scope.
scopeKey = this.siteScopeKeyFromURL(url);
- scope = this.removeTemporaryScope(scopeKey);
- if ( scope ) {
- this.temporaryScopeJunkyard[scopeKey] = scope;
+ this.removeTemporaryScopeFromScopeKey(scopeKey);
+ if ( scopeKey.indexOf('https:') === 0 ) {
+ scopeKey = 'http:' + scopeKey.slice(6);
+ this.removeTemporaryScopeFromScopeKey(scopeKey);
}
};
@@ -253,10 +167,15 @@ HTTPSB.createPermanentDomainScope = function(url) {
// Remove potentially existing site scope: it would occlude domain scope.
scopeKey = this.siteScopeKeyFromURL(url);
- scope = this.removePermanentScope(scopeKey);
- if ( scope ) {
+ if ( this.removePermanentScopeFromScopeKey(scopeKey) ) {
changed = true;
}
+ if ( scopeKey.indexOf('https:') === 0 ) {
+ scopeKey = 'http:' + scopeKey.slice(6);
+ if ( this.removeTemporaryScopeFromScopeKey(scopeKey) ) {
+ changed = true;
+ }
+ }
if ( changed ) {
this.savePermissions();
@@ -267,24 +186,16 @@ HTTPSB.createPermanentDomainScope = function(url) {
/******************************************************************************/
HTTPSB.createTemporarySiteScope = function(url) {
- var scopeKey, scope;
-
// Already created?
- scopeKey = this.siteScopeKeyFromURL(url);
- if ( this.temporaryScopes.scopes[scopeKey] ) {
- return false;
- }
-
- // See if there is a match in junkyard
- scope = this.temporaryScopeJunkyard[scopeKey];
+ var scopeKey = this.siteScopeKeyFromURL(url);
+ var scope = this.temporaryScopes.scopes[scopeKey];
if ( !scope ) {
scope = new PermissionScope();
scope.whitelist('main_frame', '*');
+ this.temporaryScopes.scopes[scopeKey] = scope;
} else {
- delete this.temporaryScopeJunkyard[scopeKey];
+ scope.off = false;
}
- this.temporaryScopes.scopes[scopeKey] = scope;
- return true;
};
HTTPSB.createPermanentSiteScope = function(url) {
@@ -302,31 +213,44 @@ HTTPSB.createPermanentSiteScope = function(url) {
/******************************************************************************/
-HTTPSB.removeTemporaryScope = function(scopeKey) {
+HTTPSB.createTemporaryScopeFromScopeKey = function(scopeKey, empty) {
var scope = this.temporaryScopes.scopes[scopeKey];
- if ( scope ) {
- delete this.temporaryScopes.scopes[scopeKey];
+ if ( !scope ) {
+ scope = new PermissionScope();
+ scope.whitelist('main_frame', '*');
+ this.temporaryScopes.scopes[scopeKey] = scope;
+ } else if ( scope.off ) {
+ scope.off = false;
}
return scope;
};
-HTTPSB.removePermanentScope = function(scopeKey) {
- var scope = this.permanentScopes.scopes[scopeKey];
+/******************************************************************************/
+
+HTTPSB.removeTemporaryScopeFromScopeKey = function(scopeKey) {
+ if ( scopeKey === '*' ) {
+ return null;
+ }
+ var scope = this.temporaryScopes.scopes[scopeKey];
if ( scope ) {
- delete this.permanentScopes.scopes[scopeKey];
+ scope.off = true;
}
return scope;
};
-/******************************************************************************/
-
-HTTPSB.removePermanentScope = function(scopeKey) {
- var scope = this.permanentScopes.scopes[scopeKey];
- if ( !scope ) {
+HTTPSB.removePermanentScopeFromScopeKey = function(scopeKey, persist) {
+ // Can't remove global scope
+ if ( scopeKey === '*' ) {
return null;
}
- delete this.permanentScopes.scopes[scopeKey];
- return scope;
+ var pscope = this.permanentScopes.scopes[scopeKey];
+ if ( pscope ) {
+ delete this.permanentScopes.scopes[scopeKey];
+ if ( persist ) {
+ this.savePermissions();
+ }
+ }
+ return pscope;
};
/******************************************************************************/
@@ -377,6 +301,16 @@ HTTPSB.transposeType = function(type, url) {
/******************************************************************************/
+HTTPSB.addRuleTemporarily = function(scopeKey, list, type, hostname) {
+ this.temporaryScopes.addRule(scopeKey, list, type, hostname);
+};
+
+HTTPSB.removeRuleTemporarily = function(scopeKey, list, type, hostname) {
+ this.temporaryScopes.removeRule(scopeKey, list, type, hostname);
+};
+
+/******************************************************************************/
+
// Whitelist something
HTTPSB.whitelistTemporarily = function(scopeKey, type, hostname) {
@@ -496,6 +430,17 @@ HTTPSB.getPermanentColor = function(scopeKey, type, hostname) {
/******************************************************************************/
+// Commit temporary permissions.
+
+HTTPSB.commitPermissions = function(persist) {
+ this.permanentScopes.assign(this.temporaryScopes);
+ if ( persist ) {
+ this.savePermissions();
+ }
+};
+
+/******************************************************************************/
+
// Reset permission lists to their default state.
HTTPSB.revertPermissions = function() {
diff --git a/js/lists.js b/js/lists.js
index 59d0ef3..15cd4cf 100644
--- a/js/lists.js
+++ b/js/lists.js
@@ -188,6 +188,7 @@ PermissionScope.prototype.assign = function(other) {
this.white.assign(other.white);
this.black.assign(other.black);
this.gray.assign(other.gray);
+ this.off = other.off;
};
/******************************************************************************/
@@ -380,6 +381,24 @@ PermissionScope.prototype.evaluate = function(type, hostname) {
/******************************************************************************/
+PermissionScope.prototype.addRule = function(list, type, hostname) {
+ var list = this[list];
+ 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];
+ if ( !list ) {
+ throw new Error('PermissionScope.removeRule() > invalid list name');
+ }
+ return list.removeOne(type + '|' + hostname);
+};
+
+/******************************************************************************/
+
PermissionScope.prototype.whitelist = function(type, hostname) {
var key = type + '|' + hostname;
var changed = false;
@@ -470,6 +489,7 @@ PermissionScopes.prototype.fromString = function(s) {
PermissionScopes.prototype.assign = function(other) {
var scopeKeys, i, scopeKey;
+ var thisScope, otherScope;
// Remove scopes found here but not found in other
// Overwrite scopes found both here and in other
@@ -477,10 +497,14 @@ PermissionScopes.prototype.assign = function(other) {
i = scopeKeys.length;
while ( i-- ) {
scopeKey = scopeKeys[i];
- if ( !other.scopes[scopeKey] ) {
+ otherScope = other.scopes[scopeKey];
+ if ( otherScope && otherScope.off ) {
+ otherScope = null;
+ }
+ if ( !otherScope ) {
delete this.scopes[scopeKey];
} else {
- this.scopes[scopeKey].assign(other.scopes[scopeKey]);
+ this.scopes[scopeKey].assign(otherScope);
}
}
@@ -489,7 +513,15 @@ PermissionScopes.prototype.assign = function(other) {
i = scopeKeys.length;
while ( i-- ) {
scopeKey = scopeKeys[i];
- if ( !this.scopes[scopeKey] ) {
+ otherScope = other.scopes[scopeKey];
+ if ( otherScope.off ) {
+ continue;
+ }
+ thisScope = this.scopes[scopeKey];
+ if ( thisScope && thisScope.off ) {
+ thisScope = null;
+ }
+ if ( !thisScope ) {
this.scopes[scopeKey] = new PermissionScope();
this.scopes[scopeKey].assign(other.scopes[scopeKey]);
}
@@ -514,13 +546,19 @@ PermissionScopes.prototype.scopeKeyFromPageURL = function(url) {
// From narrowest scope to broadest scope
// Try site scope
var scopeKey = scheme + '://' + hostname;
- if ( this.scopes[scopeKey] ) {
+ var scope = this.scopes[scopeKey];
+ if ( scope && !scope.off ) {
return scopeKey;
}
var secure = scheme === 'https';
+ // If the connection is encrypted, it is then acceptable to apply
+ // rules from unencrypted connection if any, because it is assumed the
+ // rules for unencrypted connection are more restrictive.
+ // The reverse is not true however.
if ( secure ) {
scopeKey = 'http://' + hostname;
- if ( this.scopes[scopeKey] ) {
+ scope = this.scopes[scopeKey];
+ if ( scope && !scope.off ) {
return scopeKey;
}
}
@@ -530,12 +568,14 @@ PermissionScopes.prototype.scopeKeyFromPageURL = function(url) {
return '*';
}
scopeKey = scheme + '://*.' + domain;
- if ( this.scopes[scopeKey] ) {
+ scope = this.scopes[scopeKey];
+ if ( scope && !scope.off ) {
return scopeKey;
}
if ( secure ) {
scopeKey = 'http://*.' + domain;
- if ( this.scopes[scopeKey] ) {
+ scope = this.scopes[scopeKey];
+ if ( scope && !scope.off ) {
return scopeKey;
}
}
@@ -563,6 +603,14 @@ PermissionScopes.prototype.evaluate = function(scopeKey, type, hostname) {
/******************************************************************************/
+PermissionScopes.prototype.addRule = function(scopeKey, list, type, hostname) {
+ return this.scopeFromScopeKey(scopeKey).addRule(list, type, hostname);
+};
+
+PermissionScopes.prototype.removeRule = function(scopeKey, list, type, hostname) {
+ return this.scopeFromScopeKey(scopeKey).removeRule(list, type, hostname);
+};
+
PermissionScopes.prototype.whitelist = function(scopeKey, type, hostname) {
return this.scopeFromScopeKey(scopeKey).whitelist(type, hostname);
};
diff --git a/js/popup.js b/js/popup.js
index e7fe2e8..43d8031 100644
--- a/js/popup.js
+++ b/js/popup.js
@@ -1362,8 +1362,8 @@ function bindToTabHandler(tabs) {
if ( !HTTPSBPopup.matrixHasRows ) {
$('#no-traffic').css('display', '');
$('#matHead').remove();
- $('#scopeMenu').remove();
- $('#scopePersist').remove();
+ $('#toolbarScope').remove();
+ $('#buttonPersist').remove();
$('#buttonReload').remove();
$('#buttonRevert').remove();
}
@@ -1413,6 +1413,18 @@ function gotoExtensionURL() {
/******************************************************************************/
+function gotoExternalURL() {
+ var url = $(this).data('externalUrl');
+ if ( url ) {
+ chrome.runtime.sendMessage({
+ what: 'gotoURL',
+ url: url
+ });
+ }
+}
+
+/******************************************************************************/
+
// make menu only when popup html is fully loaded
function initAll() {
@@ -1454,7 +1466,7 @@ function initAll() {
$('body')
.on('mouseenter', '.matCell', mouseenterMatrixCellHandler)
.on('mouseleave', '.matCell', mouseleaveMatrixCellHandler);
- $('#scopePersist').on('click', persistScope);
+ $('#buttonPersist').on('click', persistScope);
$('#scopeKeyGlobal').on('mousedown', createGlobalScope);
$('#scopeKeyDomain').on('mousedown', createDomainScope);
$('#scopeKeySite').on('mousedown', createSiteScope);
@@ -1466,8 +1478,9 @@ function initAll() {
$('#buttonRuleManager span').text(chrome.i18n.getMessage('ruleManagerPageName'));
$('#buttonInfo span').text(chrome.i18n.getMessage('statsPageName'));
$('#buttonSettings span').text(chrome.i18n.getMessage('settingsPageName'));
- $('.extensionURL').on('click', gotoExtensionURL);
- $('#buttonPower').on('click', togglePower);
+ $('.extensionURL').on('mousedown', gotoExtensionURL);
+ $('.externalURL').on('mousedown', gotoExternalURL);
+ $('#buttonPower').on('mousedown', togglePower);
$('#matList').on('click', '.g3Meta', function() {
var separator = $(this);
separator.toggleClass('g3Collapsed');
diff --git a/js/rulemanager.js b/js/rulemanager.js
index 2ecec39..253baf7 100644
--- a/js/rulemanager.js
+++ b/js/rulemanager.js
@@ -19,9 +19,6 @@
Home: https://github.com/gorhill/httpswitchboard
*/
-
-// TODO: cleanup
-
/******************************************************************************/
(function() {
@@ -31,26 +28,14 @@ var recipeWidth = 40;
/******************************************************************************/
var friendlyTypeNames = {
- '*': '*',
- 'cookie': 'cookies',
+ '*': '\u2217',
+ 'cookie': 'cookie',
'stylesheet': 'css',
- 'image': 'images',
- 'object': 'plugins',
- 'script': 'scripts',
- 'xmlhttprequest': 'XMLHttpRequests',
- 'sub_frame': 'frames',
- 'other': 'other'
-};
-
-var hostileTypeNames = {
- '*': '*',
- 'cookies': 'cookie',
- 'css': 'stylesheet',
- 'images': 'image',
- 'plugins': 'object',
- 'scripts': 'script',
- 'XMLHttpRequests': 'xmlhttprequest',
- 'frames': 'sub_frame',
+ 'image': 'img',
+ 'object': 'plugin',
+ 'script': 'script',
+ 'xmlhttprequest': 'XHR',
+ 'sub_frame': 'frame',
'other': 'other'
};
@@ -119,7 +104,7 @@ function renderRecipeStringToListKey(recipe) {
if ( !parts ) {
return false;
}
- return parts[1];
+ return parts[1].replace('list', '');
}
/******************************************************************************/
@@ -164,27 +149,6 @@ function renderAllScopesToRecipeString(scopes) {
/******************************************************************************/
-function renderRuleToHTML(rule) {
- // part[0] = type
- // part[1] = hostname
- var parts = rule.split('|');
- return document.createTextNode(friendlyTypeNames[parts[0]] + ' ' + (parts[1] === '*' ? '*' : parts[1]));
-}
-
-/******************************************************************************/
-
-function renderScopeKeyToHTML(scopeKey) {
- if ( scopeKey === '*' ) {
- return $('*');
- }
- return $('', {
- href: scopeKey,
- text: scopeKey
- });
-}
-
-/******************************************************************************/
-
function uglifyRecipe(recipe) {
recipe = encodeURIComponent(recipe.replace(/ /g, '\t'));
var s = '';
@@ -219,11 +183,13 @@ function beautifyRecipe(recipe) {
/******************************************************************************/
-function getPermanentColor(scopeKey, rule) {
- // part[0] = type
- // part[1] = hostname
- var parts = rule.split('|');
- return getHTTPSB().getPermanentColor(scopeKey, parts[0], parts[1]);
+function renderRuleKeyToHTML(rule) {
+ var pos = rule.indexOf('|');
+ return document.createTextNode(
+ friendlyTypeNames[rule.slice(0, pos)] +
+ ' ' +
+ rule.slice(pos + 1).replace('*', '\u2217')
+ );
}
/******************************************************************************/
@@ -251,49 +217,123 @@ function compareRules(a, b) {
/******************************************************************************/
+function renderScopeKeyToHTML(scopeKey) {
+ var div = $('');
- liScope.append(ulScope);
+ return liScope;
+}
+
+/******************************************************************************/
+
+function renderListToHTML(listKey) {
+ var liList = $('
');
+ liScope.append(ulLists);
+ var lists = ['gray', 'black', 'white'];
var iList = lists.length;
- var rules, iRule, rule;
- var liList, ulList, liRule;
+ var listKey, tlist, ulLists, liList;
+ var rules, iRule, ruleKey, ulRules, liRule;
while ( iList-- ) {
- rules = Object.keys(scope[lists[iList]].list).sort(compareRules);
+ listKey = lists[iList];
+ tlist = tscope[listKey].list;
+ rules = Object.keys(tlist).sort(compareRules);
iRule = rules.length;
- if ( iRule === 0 ) {
- continue;
- }
- liList = $('
', {});
+ liList = renderListToHTML(listKey);
+ ulRules = $('
', {});
while ( iRule-- ) {
- rule = rules[iRule];
+ ruleKey = rules[iRule];
// Skip '* main_frame', there is no matrix cell for this, which
- // means user wouldn't be able to add it back if he/she were to
+ // means user wouldn't be able to add it back if user were to
// remove this rule.
- if ( rule === 'main_frame|*' ) {
+ if ( ruleKey === 'main_frame|*' ) {
continue;
}
- liRule = $('
');
- // Iterate scopes
- var scopeKeys = Object.keys(httpsb.temporaryScopes.scopes);
- var iScope = scopeKeys.length;
- var scopeKey, scope;
- var liScope;
- while ( iScope-- ) {
- scopeKey = scopeKeys[iScope];
- if ( scopeKey === '*' ) {
+ var pscope = httpsb.permanentScopes.scopes[scopeKey];
+ var liScope = liScopeFromScopeKey(scopeKey);
+ if ( liScope ) {
+ liScope.addClass('permanent');
+ } else {
+ // ???
+ }
+ var lists = ['gray', 'black', 'white'];
+ var iList = lists.length;
+ var listKey, plist;
+ var rules, iRule, ruleKey, liRule;
+ while ( iList-- ) {
+ listKey = lists[iList];
+ plist = pscope[listKey].list;
+ rules = Object.keys(plist).sort(compareRules);
+ iRule = rules.length;
+ while ( iRule-- ) {
+ ruleKey = rules[iRule];
+ // Skip '* main_frame', there is no matrix cell for this, which
+ // means user wouldn't be able to add it back if user were to
+ // remove this rule.
+ if ( ruleKey === 'main_frame|*' ) {
+ continue;
+ }
+ liRule = liRuleFromRuleKey(scopeKey, listKey, ruleKey);
+ if ( liRule ) {
+ liRule.addClass('permanent');
+ } else {
+ liRule = renderRuleToHTML(ruleKey, false)
+ liRule.appendTo(liListFromScopeKey(scopeKey, 'gray').children('ul'));
+ }
+ }
+ }
+}
+
+/******************************************************************************/
+
+function renderScopes(domContainerId, filterFn) {
+ var httpsb = getHTTPSB();
+ var scopes = httpsb.temporaryScopes.scopes;
+ var ulScopes = $('
');
+ var scope, liScope, scopeKey;
+ for ( scopeKey in scopes ) {
+ if ( !scopes.hasOwnProperty(scopeKey) ) {
+ continue;
+ }
+ if ( !filterFn(httpsb, scopeKey) ) {
continue;
}
- scope = httpsb.temporaryScopes.scopes[scopeKey];
+ scope = scopes[scopeKey];
if ( scope.off ) {
continue;
}
- liScope = renderScopeToHTML(scopeKey);
- liScope.appendTo(ulRoot);
+ liScope = renderTemporaryScopeTreeToHTML(scopeKey);
+ liScope.appendTo(ulScopes);
+ }
+ $(domContainerId).empty().append(ulScopes);
+ scopes = httpsb.permanentScopes.scopes;
+ for ( scopeKey in scopes ) {
+ if ( !scopes.hasOwnProperty(scopeKey) ) {
+ continue;
+ }
+ if ( !filterFn(httpsb, scopeKey) ) {
+ continue;
+ }
+ scope = scopes[scopeKey];
+ renderPermanentScopeTreeToHTML(scopeKey);
}
- $('#persite').empty().append(ulRoot);
+}
+
+/******************************************************************************/
+
+function renderSiteScopes() {
+ var filterFn = function(httpsb, scopeKey) {
+ return httpsb.isSiteScopeKey(scopeKey);
+ };
+ renderScopes('#persite', filterFn);
+}
+
+/******************************************************************************/
+
+function renderDomainScopes() {
+ var filterFn = function(httpsb, scopeKey) {
+ return httpsb.isDomainScopeKey(scopeKey);
+ };
+ renderScopes('#perdomain', filterFn);
}
/******************************************************************************/
function renderGlobalScope() {
- var ulRoot = $('
');
- var liScope = renderScopeToHTML('*');
- liScope.appendTo(ulRoot);
- $('#global').empty().append(ulRoot);
+ var filterFn = function(httpsb, scopeKey) {
+ return httpsb.isGlobalScopeKey(scopeKey);
+ };
+ renderScopes('#global', filterFn);
}
/******************************************************************************/
@@ -346,7 +453,9 @@ function renderRecipe() {
function renderAll() {
renderGlobalScope();
- renderPersiteScopes();
+ renderDomainScopes();
+ renderSiteScopes();
+ updateButtons();
}
/******************************************************************************/
@@ -361,16 +470,6 @@ function selectRecipeText(elem) {
/******************************************************************************/
-function deleteRule(li) {
-}
-
-/******************************************************************************/
-
-function undeleteRule(li) {
-}
-
-/******************************************************************************/
-
function renderJournalFromImportField() {
var rules = $('#recipeBeautiful').val();
var journal = [];
@@ -433,14 +532,8 @@ function applyJournalTemporarily() {
}
type = entry.rule.slice(0, pivot);
hostname = entry.rule.slice(pivot+1);
- httpsb.createPageScopeIfNotExists(scopeKey);
- if ( entry.listKey === 'whitelist' ) {
- httpsb.whitelistTemporarily(scopeKey, type, hostname);
- } else if ( entry.listKey === 'blacklist' ) {
- httpsb.blacklistTemporarily(scopeKey, type, hostname);
- } else if ( entry.listKey === 'graylist' ) {
- httpsb.graylistTemporarily(scopeKey, type, hostname);
- }
+ httpsb.createTemporaryScopeFromScopeKey(scopeKey);
+ httpsb.addRuleTemporarily(scopeKey, entry.listKey, type, hostname);
}
// Force a refresh of all scopes/rules
@@ -449,36 +542,108 @@ function applyJournalTemporarily() {
/******************************************************************************/
-function togglePersist(liRule) {
+function toggleDeleteScope(event) {
+ var liScope = $(this).parents('.scope');
+ liScope.toggleClass('todelete');
+ liScope.find('.rule').removeClass('todelete');
+ updateButtons();
+ event.stopPropagation();
+}
+
+/******************************************************************************/
+
+function toggleDeleteRule(event) {
+ $(this).toggleClass('todelete');
+ updateButtons();
+ event.stopPropagation();
+}
+
+/******************************************************************************/
+
+function commitAll() {
var httpsb = getHTTPSB();
- liRule = $(liRule);
- var rule = liRule.text();
- // parts[0] = friendly type name
- // parts[1] = hostname
- var parts = rule.split(/\s+/);
- if ( parts.length !== 2 ) {
- return;
+ var i;
+ var liScope, scopeKey;
+ var liList;
+ var liRule, rule, pos, type, hostname;
+
+ // Delete scopes marked for deletion
+ var liScopes = $('.scope.todelete');
+ i = liScopes.length;
+ while ( i-- ) {
+ liScope = $(liScopes[i]);
+ scopeKey = liScope.prop('scopeKey');
+ if ( scopeKey === '*' ) {
+ continue;
+ }
+ httpsb.removeTemporaryScopeFromScopeKey(scopeKey);
+ liScope.remove();
}
- var hostname = parts[1];
- var type = hostileTypeNames[parts[0]];
- var liScope = liRule.parents('li.scope');
- var scopeKey = liScope.children('a').attr('href') || '*';
- if ( liRule.hasClass('rdp') || liRule.hasClass('gdp') ) {
- httpsb.graylistPermanently(scopeKey, type, hostname);
- liRule.removeClass('rdp gdp');
- } else if ( liRule.parents('li.white').length ) {
- httpsb.whitelistPermanently(scopeKey, type, hostname);
- liRule.addClass('gdp');
- } else if ( liRule.parents('li.black').length ) {
- httpsb.blacklistPermanently(scopeKey, type, hostname);
- liRule.addClass('rdp');
+
+ // Delete rules marked for deletion
+ var liRules = $('.scope.todelete .rule,.rule.todelete');
+ i = liRules.length;
+ while ( i-- ) {
+ liRule = $(liRules[i]);
+ liList = liRule.parents('.list');
+ liScope = liList.parents('.scope');
+ rule = liRule.prop('rule');
+ pos = rule.indexOf('|');
+ type = rule.slice(0, pos);
+ hostname = rule.slice(pos + 1);
+ httpsb.removeRuleTemporarily(
+ liScope.prop('scopeKey'),
+ liList.prop('listKey'),
+ type,
+ hostname
+ );
}
+
+ // Persist whatever is left
+ httpsb.commitPermissions(true);
+ renderAll();
+}
+
+/******************************************************************************/
+
+function revertAll() {
+ var httpsb = getHTTPSB();
+ httpsb.revertPermissions();
renderAll();
}
/******************************************************************************/
+function removeAll() {
+ $('.scope').addClass('todelete');
+ if ( !confirm($('#confirmRemoveAll').text()) ) {
+ $('.scope').removeClass('todelete');
+ return;
+ }
+ commitAll();
+}
+
+/******************************************************************************/
+
+function updateButtons() {
+ var notOneTemporary = $('.scope:not(.permanent),.scope.permanent .rule:not(.permanent)').length === 0;
+ var notOneDeletion = $('.todelete').length === 0;
+ $('#commitAll').prop("disabled", notOneTemporary && notOneDeletion);
+ $('#revertAll').prop("disabled", notOneTemporary);
+ $('#removeAll').prop("disabled", $('.scope').length <= 1);
+}
+
+/******************************************************************************/
+
$(function() {
+ $('#commitAll').on('click', commitAll);
+
+ // Toggle permanent scope status
+ $('#revertAll').on('click', revertAll);
+
+ // Toggle permanent scope status
+ $('#removeAll').on('click', removeAll);
+
$('#recipeDecode').on('click', function(){
if ( !$('#recipeUgly').hasClass('bad') ) {
renderImportFieldFromRecipe();
@@ -510,7 +675,7 @@ $(function() {
});
// Auto-select all encoded recipe
- $('body').on('click', '#recipeUgly', function(){
+ $('#recipeUgly').on('click', function(){
this.focus();
this.select();
});
@@ -520,9 +685,12 @@ $(function() {
selectRecipeText(this);
});
- // Toggle permanent status
- $('.scopes').on('click', '.rule', function(){
- togglePersist($(this));
+ // Toggle deletion
+ $('body').on('click', '.rule', toggleDeleteRule);
+ $('body').on('click', '.scopeName', toggleDeleteScope);
+
+ $('#bye').on('click', function() {
+ window.open('','_self').close();
});
renderAll();
diff --git a/manifest.json b/manifest.json
index 9ff9dd8..79f709f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "__MSG_extName__",
- "version": "0.7.1.2",
+ "version": "0.7.2.0",
"description": "__MSG_extShortDesc__",
"icons": {
"16": "icon_16.png",
diff --git a/popup.html b/popup.html
index c5a8362..69dbed4 100644
--- a/popup.html
+++ b/popup.html
@@ -4,7 +4,6 @@
-
Consider this feature early beta, use at your own risk. Useful to backup - all of your rules.
-Rules@@ -205,14 +49,20 @@HTTP Switchboard — Rule manager |
Here are the current rules which apply by default.
+Here are the current rules which apply to specific web sites.
+diff --git a/rulemanager.html b/rulemanager.html index c9891c3..7a80cb4 100644 --- a/rulemanager.html +++ b/rulemanager.html @@ -3,174 +3,18 @@
- - + +
diff --git a/settings.html b/settings.html index 4c3427e..8ad9586 100644 --- a/settings.html +++ b/settings.html @@ -67,37 +67,43 @@
+