From af7bd5ee343e649ca37d24c7ac3c0d60b2dda7d7 Mon Sep 17 00:00:00 2001 From: Chan Chak Shing Date: Wed, 17 Oct 2018 05:31:54 +0800 Subject: [PATCH] Add UI in option page to view and remove user rules (#16618) --- chromium/background-scripts/background.js | 22 +++++- chromium/background-scripts/rules.js | 87 ++++++++++++++--------- chromium/background-scripts/store.js | 58 ++++++++++----- chromium/pages/options/index.html | 3 + chromium/pages/options/style.css | 18 +++++ chromium/pages/options/ux.js | 35 +++++++++ chromium/pages/popup/ux.js | 13 ++-- src/chrome/locale/en/https-everywhere.dtd | 1 + 8 files changed, 181 insertions(+), 56 deletions(-) diff --git a/chromium/background-scripts/background.js b/chromium/background-scripts/background.js index f0b9ba36cdcf..25ac4e4e279d 100644 --- a/chromium/background-scripts/background.js +++ b/chromium/background-scripts/background.js @@ -887,6 +887,10 @@ 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); @@ -894,7 +898,23 @@ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){ 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)); diff --git a/chromium/background-scripts/rules.js b/chromium/background-scripts/rules.js index 5eba395ae6c3..6b42cc6885a5 100644 --- a/chromium/background-scripts/rules.js +++ b/chromium/background-scripts/rules.js @@ -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(); }, @@ -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"; } @@ -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; }, @@ -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; }, @@ -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`); + }); }, /** @@ -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); } }, @@ -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"; } diff --git a/chromium/background-scripts/store.js b/chromium/background-scripts/store.js index 3b555c7311d5..c41ccd0a5cc2 100644 --- a/chromium/background-scripts/store.js +++ b/chromium/background-scripts/store.js @@ -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 = { diff --git a/chromium/pages/options/index.html b/chromium/pages/options/index.html index aee189319074..f351d1773c45 100644 --- a/chromium/pages/options/index.html +++ b/chromium/pages/options/index.html @@ -19,6 +19,9 @@ +
+

+

diff --git a/chromium/pages/options/style.css b/chromium/pages/options/style.css index 6bd1b385e0ef..d1ff21e51798 100644 --- a/chromium/pages/options/style.css +++ b/chromium/pages/options/style.css @@ -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; diff --git a/chromium/pages/options/ux.js b/chromium/pages/options/ux.js index c9bbc8a6e705..c566763e9bb8 100644 --- a/chromium/pages/options/ux.js +++ b/chromium/pages/options/ux.js @@ -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"); diff --git a/chromium/pages/popup/ux.js b/chromium/pages/popup/ux.js index 2c4ab9392b72..f50a928e9c25 100644 --- a/chromium/pages/popup/ux.js +++ b/chromium/pages/popup/ux.js @@ -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); }); }); @@ -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(); diff --git a/src/chrome/locale/en/https-everywhere.dtd b/src/chrome/locale/en/https-everywhere.dtd index c7534a5784b8..3b4b02e3a41c 100644 --- a/src/chrome/locale/en/https-everywhere.dtd +++ b/src/chrome/locale/en/https-everywhere.dtd @@ -18,6 +18,7 @@ +