Skip to content

Commit

Permalink
Add UI in option page to view and remove user rules (EFForg#16618)
Browse files Browse the repository at this point in the history
  • Loading branch information
Chan Chak Shing authored and Hainish committed Oct 16, 2018
1 parent 26ba9c0 commit af7bd5e
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 56 deletions.
22 changes: 21 additions & 1 deletion chromium/background-scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -887,14 +887,34 @@ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
chrome.tabs.reload();
});
},
get_user_rules: () => {
store.get_promise(all_rules.USER_RULE_KEY, []).then(userRules => sendResponse(userRules));
return true;
},
add_new_rule: () => {
all_rules.addNewRuleAndStore(message.object).then(() => {
sendResponse(true);
});
return true;
},
remove_rule: () => {
all_rules.removeRuleAndStore(message.object);
all_rules.removeRuleAndStore(message.object.ruleset, message.object.src)
.then(() => {
/**
* FIXME: initializeAllRules is needed for calls from the option pages.
* Since message.object is not of type Ruleset, rules.removeUserRule
* is not usable...
*/
if (message.object.src === 'options') {
return initializeAllRules();
}
})
.then(() => {
if (sendResponse !== null) {
sendResponse(true);
}
})
return true;
},
get_ruleset_timestamps: () => {
update.getRulesetTimestamps().then(timestamps => sendResponse(timestamps));
Expand Down
87 changes: 54 additions & 33 deletions chromium/background-scripts/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ RuleSets.prototype = {
this.store = store;
this.ruleActiveStates = await this.store.get_promise('ruleActiveStates', {});
await applyStoredFunc(this);
this.loadStoredUserRules();
await this.loadStoredUserRules();
await this.addStoredCustomRulesets();
},

Expand Down Expand Up @@ -257,6 +257,9 @@ RuleSets.prototype = {
var default_off = ruletag["default_off"];
if (default_off) {
default_state = false;
if (default_off === "user rule") {
default_state = true;
}
note += default_off + "\n";
}

Expand Down Expand Up @@ -320,18 +323,14 @@ RuleSets.prototype = {
*/
addUserRule : function(params, scope) {
util.log(util.INFO, 'adding new user rule for ' + JSON.stringify(params));
var new_rule_set = new RuleSet(params.host, true, scope, "user rule");
var new_rule = getRule(params.urlMatcher, params.redirectTo);
new_rule_set.rules.push(new_rule);
if (!this.targets.has(params.host)) {
this.targets.set(params.host, []);
this.parseOneJsonRuleset(params, scope);

// clear cache so new rule take effect immediately
for (const target of params.target) {
this.ruleCache.delete(target);
}
this.ruleCache.delete(params.host);

// TODO: maybe promote this rule?
this.targets.get(params.host).push(new_rule_set);
if (new_rule_set.name in this.ruleActiveStates) {
new_rule_set.active = this.ruleActiveStates[new_rule_set.name];
}
util.log(util.INFO, 'done adding rule');
return true;
},
Expand All @@ -341,20 +340,33 @@ RuleSets.prototype = {
* @param params
* @returns {boolean}
*/
removeUserRule: function(ruleset) {
removeUserRule: function(ruleset, src) {
/**
* FIXME: We have to use ruleset.name here because the ruleset itself
* carries no information on the target it is applying on. This also
* made it impossible for users to set custom ruleset name.
*/
util.log(util.INFO, 'removing user rule for ' + JSON.stringify(ruleset));
this.ruleCache.delete(ruleset.name);

// Remove any cache from runtime
this.ruleCache.delete(ruleset.name);

var tmp = this.targets.get(ruleset.name).filter(r =>
!(r.isEquivalentTo(ruleset))
);
this.targets.set(ruleset.name, tmp);
if (src === 'popup') {
const tmp = this.targets.get(ruleset.name).filter(r => !r.isEquivalentTo(ruleset))
this.targets.set(ruleset.name, tmp);

if (this.targets.get(ruleset.name).length == 0) {
this.targets.delete(ruleset.name);
if (this.targets.get(ruleset.name).length == 0) {
this.targets.delete(ruleset.name);
}
}

if (src === 'options') {
/**
* FIXME: There is nothing we can do if the call comes from the
* option page because isEquivalentTo cannot work reliably.
* Leave the heavy duties to background.js to call initializeAllRules
*/
}
util.log(util.INFO, 'done removing rule');
return true;
},
Expand All @@ -369,13 +381,12 @@ RuleSets.prototype = {
/**
* Load all stored user rules into this RuleSet object
*/
loadStoredUserRules: async function() {
const scope = getScope();
const user_rules = await this.getStoredUserRules();
for (let user_rule of user_rules) {
this.addUserRule(user_rule, scope);
}
util.log(util.INFO, 'loaded ' + user_rules.length + ' stored user rules');
loadStoredUserRules: function() {
return this.getStoredUserRules()
.then(userRules => {
this.addFromJson(userRules, getScope());
util.log(util.INFO, `loaded ${userRules.length} stored user rules`);
});
},

/**
Expand All @@ -401,14 +412,21 @@ RuleSets.prototype = {
* Removes a user rule
* @param ruleset: the ruleset to remove
* */
removeRuleAndStore: async function(ruleset) {
if (this.removeUserRule(ruleset)) {
// If we successfully removed the user rule, remove it in local storage too
removeRuleAndStore: async function(ruleset, src) {
if (this.removeUserRule(ruleset, src)) {
let userRules = await this.getStoredUserRules();
userRules = userRules.filter(r =>
!(r.host == ruleset.name &&
r.redirectTo == ruleset.rules[0].to)
);

if (src === 'popup') {
userRules = userRules.filter(r =>
!(r.name === ruleset.name && r.rule[0].to === ruleset.rules[0].to)
);
}

if (src === 'options') {
userRules = userRules.filter(r =>
!(r.name === ruleset.name && r.rule[0].to === ruleset.rule[0].to)
);
}
await this.store.set_promise(this.USER_RULE_KEY, userRules);
}
},
Expand Down Expand Up @@ -456,6 +474,9 @@ RuleSets.prototype = {
var default_off = ruletag.getAttribute("default_off");
if (default_off) {
default_state = false;
if (default_off === "user rule") {
default_state = true;
}
note += default_off + "\n";
}

Expand Down
58 changes: 40 additions & 18 deletions chromium/background-scripts/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,29 +51,51 @@ function local_set_promise(key, value) {


async function performMigrations() {
const migration_version = await get_promise('migration_version', 0);

if (migration_version < 1) {

let ls;
try {
ls = localStorage;
} catch(e) {}

let ruleActiveStates = {};
for (const key in ls) {
if (ls.hasOwnProperty(key)) {
if (key == rules.RuleSets().USER_RULE_KEY){
await set_promise(rules.RuleSets().USER_RULE_KEY, JSON.parse(ls[key]));
} else {
ruleActiveStates[key] = (ls[key] == "true");
let migration_version = await get_promise('migration_version', 0);

try {
if (migration_version === 0) {
let ls = localStorage;
let ruleActiveStates = {};

for (let key in ls) {
if (ls.hasOwnProperty(key)) {
if (rules.RuleSets().USER_RULE_KEY === key) {
await set_promise(rules.RuleSets().USER_RULE_KEY, JSON.parse(ls[key]));
} else {
ruleActiveStates[key] = (ls[key] === "true");
}
}
}
migration_version = 1;
await set_promise('migration_version', migration_version);
await set_promise('ruleActiveStates', ruleActiveStates);
}
await set_promise('ruleActiveStates', ruleActiveStates);

} catch (e) {
// do nothing
}

await set_promise('migration_version', 1);
if (migration_version <= 1) {
await get_promise(rules.RuleSets().USER_RULE_KEY, [])
.then(userRules => {
userRules = userRules.map(userRule => {
return {
name: userRule.host,
target: [userRule.host],
rule: [{ from: userRule.urlMatcher, to: userRule.redirectTo }],
default_off: "user rule"
}
})
return userRules;
})
.then(userRules => {
return set_promise(rules.RuleSets().USER_RULE_KEY, userRules);
})

migration_version = 2;
await set_promise('migration_version', migration_version);
}
}

const local = {
Expand Down
3 changes: 3 additions & 0 deletions chromium/pages/options/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
<input type="checkbox" id="autoUpdateRulesets">
<label for="autoUpdateRulesets" data-i18n="options_autoUpdateRulesets"></label>
</div>
<div id="user-rules-wrapper">
<p class="user-rules-wrapper-header" data-i18n="options_userRulesListed"></p>
</div>
<div id="disabled-rules-wrapper">
<p class="disabled-rules-wrapper-header" data-i18n="options_disabledUrlsListed"></p>
</div>
Expand Down
18 changes: 18 additions & 0 deletions chromium/pages/options/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ body{
margin-bottom: 20px;
}

/** User rules Option**/
.user-rules-wrapper-header {
font-weight: bold;
padding-left: 5px;
}
.user-rules-list-item:last-of-type {
border-bottom: none;
}
.user-rules-list-item {
border-bottom: 1px solid #ccc;
display: inline-flex;
margin-left: 5%;
width: 80%;
}
.user-rules-list-item p {
width: 100%;
}

/** Disabled Sites Option**/
.disabled-rules-wrapper-header {
font-weight: bold;
Expand Down
35 changes: 35 additions & 0 deletions chromium/pages/options/ux.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,41 @@ document.addEventListener("DOMContentLoaded", () => {
window.scrollTo(0,0);
}

// Get a list of user Rules
sendMessage("get_user_rules", null, userRules => {
let user_rules_parent = e("user-rules-wrapper");

if ( 0 === userRules.length) {
hide(user_rules_parent);
return ;
}

// img element "remove button"
let templateRemove = document.createElement("img");
templateRemove.src = chrome.extension.getURL("images/remove.png");
templateRemove.className = "remove";

for (const userRule of userRules) {
let user_rule_host = document.createElement("div");
let user_rule_name = document.createElement("p");
let remove = templateRemove.cloneNode(true);

user_rule_host.className = "user-rules-list-item";
user_rule_name.className = "user-rules-list-item-single"
user_rule_name.innerText = userRule.name;
user_rule_host.appendChild(user_rule_name);
user_rules_parent.appendChild(user_rule_host);
user_rule_host.appendChild(remove);

remove.addEventListener("click", () => {
// assume the removal is successful and hide ui element
hide( user_rule_host );
// remove the user rule
sendMessage("remove_rule", { ruleset: userRule, src: 'options' });
});
}
})

// HTTPS Everywhere Sites Disabled section in General Settings module
getOption_("disabledList", [], function(item) {
let rule_host_parent = e("disabled-rules-wrapper");
Expand Down
13 changes: 9 additions & 4 deletions chromium/pages/popup/ux.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function appendRulesToListDiv(rulesets, list_div) {
line.appendChild(remove);

remove.addEventListener("click", () => {
sendMessage("remove_rule", ruleset, () => {
sendMessage("remove_rule", { ruleset, src: 'popup' }, () => {
list_div.removeChild(line);
});
});
Expand Down Expand Up @@ -251,9 +251,14 @@ function addManualRule() {

e("add-new-rule-button").addEventListener("click", function() {
const params = {
host : e("new-rule-host").value,
redirectTo : e("new-rule-redirect").value,
urlMatcher : e("new-rule-regex").value
/**
* FIXME: the current implementation forbide users setting custom
* ruleset names...
*/
name: e("new-rule-host").value,
target : [e("new-rule-host").value],
rule: [{ to: e("new-rule-redirect").value, from: e("new-rule-regex").value }],
default_off: "user rule"
};
sendMessage("add_new_rule", params, function() {
location.reload();
Expand Down
1 change: 1 addition & 0 deletions src/chrome/locale/en/https-everywhere.dtd
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<!ENTITY https-everywhere.options.enableMixedRulesets "Enable mixed content rulesets">
<!ENTITY https-everywhere.options.showDevtoolsTab "Show Devtools tab">
<!ENTITY https-everywhere.options.autoUpdateRulesets "Auto-update rulesets">
<!ENTITY https-everywhere.options.userRulesListed "HTTPS Everywhere User Rules">
<!ENTITY https-everywhere.options.disabledUrlsListed "HTTPS Everywhere Sites Disabled">
<!ENTITY https-everywhere.options.updateChannelsWarning "Warning: Adding update channels can cause attackers to hijack your browser. Only edit this section if you know what you're doing!">
<!ENTITY https-everywhere.options.addUpdateChannel "Add Update Channel">
Expand Down

0 comments on commit af7bd5e

Please sign in to comment.