Add ability to add domains to an "allow all" list from the browser action, issue #6
This commit is contained in:
parent
6b0b35cdf5
commit
95480a5449
19 changed files with 507 additions and 66 deletions
|
@ -23,6 +23,7 @@
|
||||||
"valid-jsdoc": "error",
|
"valid-jsdoc": "error",
|
||||||
"prefer-const": "error",
|
"prefer-const": "error",
|
||||||
"no-template-curly-in-string": "error",
|
"no-template-curly-in-string": "error",
|
||||||
"no-unused-expressions": "error"
|
"no-unused-expressions": "error",
|
||||||
|
"no-trailing-spaces": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,14 +54,14 @@
|
||||||
rootObject.tabs.onUpdated.addListener(updateBrowserActionBadge);
|
rootObject.tabs.onUpdated.addListener(updateBrowserActionBadge);
|
||||||
rootObject.tabs.onActivated.addListener(updateBrowserActionBadge);
|
rootObject.tabs.onActivated.addListener(updateBrowserActionBadge);
|
||||||
|
|
||||||
window.setInterval(function () {
|
// window.setInterval(function () {
|
||||||
rootObject.tabs.getCurrent(function (currentTab) {
|
// rootObject.tabs.getCurrent(function (currentTab) {
|
||||||
if (currentTab === undefined) {
|
// if (currentTab === undefined) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
updateBrowserActionBadge({tabId: currentTab.id});
|
// updateBrowserActionBadge({tabId: currentTab.id});
|
||||||
});
|
// });
|
||||||
}, 1000);
|
// }, 1000);
|
||||||
|
|
||||||
// Listen for updates to the domain rules from the config page.
|
// Listen for updates to the domain rules from the config page.
|
||||||
// The two types of messages that are sent to the background page are
|
// The two types of messages that are sent to the background page are
|
||||||
|
@ -73,26 +73,56 @@
|
||||||
rootObject.runtime.onMessage.addListener(function (request, ignore, sendResponse) {
|
rootObject.runtime.onMessage.addListener(function (request, ignore, sendResponse) {
|
||||||
|
|
||||||
const [label, data] = request;
|
const [label, data] = request;
|
||||||
|
|
||||||
|
// Sent from the config page, when the "which APIs should be
|
||||||
|
// blocked for which domains" has been changed on the config page.
|
||||||
if (label === "stateUpdate") {
|
if (label === "stateUpdate") {
|
||||||
domainRules = data.domainRules;
|
domainRules = data.domainRules;
|
||||||
shouldLog = data.shouldLog;
|
shouldLog = data.shouldLog;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sent from the popup / browser action, asking for infromation about
|
||||||
|
// which blocking rules are being applied to which domains in the
|
||||||
|
// tab.
|
||||||
if (label === "rulesForDomains") {
|
if (label === "rulesForDomains") {
|
||||||
|
|
||||||
const matchHostName = domainMatcherLib.matchHostName;
|
const matchHostName = domainMatcherLib.matchHostName;
|
||||||
const matchHostNameBound = matchHostName.bind(undefined, Object.keys(domainRules));
|
const matchHostNameBound = matchHostName.bind(undefined, Object.keys(domainRules));
|
||||||
const rulesForDomains = data.map(matchHostNameBound);
|
|
||||||
const domainToRuleMapping = {};
|
const domainToRuleMapping = {};
|
||||||
|
|
||||||
data.forEach(function (aHostName, index) {
|
data.forEach(function (aHostName) {
|
||||||
domainToRuleMapping[aHostName] = rulesForDomains[index] || defaultKey;
|
const ruleNameForHost = matchHostNameBound(aHostName) || defaultKey;
|
||||||
|
domainToRuleMapping[aHostName] = {
|
||||||
|
"ruleName": ruleNameForHost,
|
||||||
|
"numRules": domainRules[ruleNameForHost].length
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
sendResponse(domainToRuleMapping);
|
sendResponse(domainToRuleMapping);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sent from the popup / browser action, saying that a given
|
||||||
|
// host name should have the default blocking rule applied
|
||||||
|
// (action === "block", or all APIs allowed
|
||||||
|
// (action === "allow").
|
||||||
|
if (label === "toggleBlocking") {
|
||||||
|
|
||||||
|
const {action, hostName} = data;
|
||||||
|
if (action === "block") {
|
||||||
|
delete domainRules[hostName];
|
||||||
|
sendResponse(["toggleBlockingResponse", domainRules[defaultKey].length]);
|
||||||
|
} else if (action === "allow") {
|
||||||
|
domainRules[hostName] = [];
|
||||||
|
sendResponse(["toggleBlockingResponse", 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
storageLib.set({
|
||||||
|
domainRules,
|
||||||
|
shouldLog
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestFilter = {
|
const requestFilter = {
|
||||||
|
|
|
@ -3,6 +3,75 @@
|
||||||
|
|
||||||
const defaultDomain = "(default)";
|
const defaultDomain = "(default)";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if two arrays contain the same values, regardless of order.
|
||||||
|
*
|
||||||
|
* @param {array} arrayOne
|
||||||
|
* One array to test.
|
||||||
|
* @param {array} arrayTwo
|
||||||
|
* The second array to test against.
|
||||||
|
*
|
||||||
|
* @return {bool}
|
||||||
|
* Returns true if the two arrays contain all of the same values,
|
||||||
|
* an otherwise false.
|
||||||
|
*/
|
||||||
|
const areArrayValuesIdentical = function (arrayOne, arrayTwo) {
|
||||||
|
if (arrayOne.length !== arrayTwo.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayOneSorted = arrayOne.sort();
|
||||||
|
const arrayTwoSorted = arrayTwo.sort();
|
||||||
|
|
||||||
|
const areAllValuesEqual = arrayOneSorted.every(function (value, index) {
|
||||||
|
return value === arrayTwoSorted[index];
|
||||||
|
});
|
||||||
|
|
||||||
|
return areAllValuesEqual;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if two domain rule sets describe the standard blocking same policy.
|
||||||
|
*
|
||||||
|
* This check is independent of the ordering of the domain matching rules
|
||||||
|
* (the keys of the rule sets), or the standards that should be blocked
|
||||||
|
* (the values in the rule sets).
|
||||||
|
*
|
||||||
|
* @param {object} firstRuleSet
|
||||||
|
* The first rule set to compare.
|
||||||
|
* @param {object} secondRuleSet
|
||||||
|
* The second rule set to compare against.
|
||||||
|
*
|
||||||
|
* @return {bool}
|
||||||
|
* Returns true if the two objects describe identical policies
|
||||||
|
* (ie would block the same standards on the same domains), and
|
||||||
|
* otherwise false.
|
||||||
|
*/
|
||||||
|
const areRuleSetsIdentical = function (firstRuleSet, secondRuleSet) {
|
||||||
|
|
||||||
|
const firstRuleSetDomains = Object.keys(firstRuleSet).sort();
|
||||||
|
const secondRuleSetDomains = Object.keys(secondRuleSet).sort();
|
||||||
|
|
||||||
|
// First check if both rule sets have the same matching patterns
|
||||||
|
// defined. If not, then no need to consider further.
|
||||||
|
const haveSameMatchPatterns = areArrayValuesIdentical(firstRuleSet, secondRuleSetDomains);
|
||||||
|
|
||||||
|
if (haveSameMatchPatterns === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, now that we know both rule sets have rules describing the
|
||||||
|
// same domains, check that standards blocked for all domains are
|
||||||
|
// the same.
|
||||||
|
return firstRuleSetDomains.every(function (value) {
|
||||||
|
if (secondRuleSet[value] === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return areArrayValuesIdentical(firstRuleSet[value], secondRuleSet[value]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const generateStateObject = function (initialDomain, standards) {
|
const generateStateObject = function (initialDomain, standards) {
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
|
@ -20,15 +89,45 @@
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
domainsBlockingNoStandards: function () {
|
||||||
|
return this.domainNames
|
||||||
|
.filter(domain => this.domainRules[domain].length === 0)
|
||||||
|
.sort();
|
||||||
|
},
|
||||||
|
|
||||||
|
domainsBlockingStandards: function () {
|
||||||
|
return this.domainNames
|
||||||
|
.filter(domain => this.domainRules[domain].length > 0)
|
||||||
|
.sort();
|
||||||
|
},
|
||||||
|
|
||||||
populateFromStorage: function (storedValues) {
|
populateFromStorage: function (storedValues) {
|
||||||
this.setDomainRules(storedValues.domainRules);
|
this.setDomainRules(storedValues.domainRules);
|
||||||
this.setShouldLog(storedValues.shouldLog);
|
this.setShouldLog(storedValues.shouldLog);
|
||||||
},
|
},
|
||||||
|
|
||||||
setDomainRules: function (newDomainRules) {
|
setDomainRules: function (newDomainRules) {
|
||||||
|
|
||||||
|
const isRuleSetMatchingCurrentRules = areRuleSetsIdentical(
|
||||||
|
newDomainRules,
|
||||||
|
this.domainRules
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the "new" domain rule set is identical to the existing
|
||||||
|
// one, then don't set any propreties (to avoid unnecessarily
|
||||||
|
// triggering storage and Vue.js callbacks).
|
||||||
|
if (isRuleSetMatchingCurrentRules === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.domainRules = newDomainRules;
|
this.domainRules = newDomainRules;
|
||||||
this.domainNames = Object.keys(newDomainRules);
|
this.domainNames = Object.keys(newDomainRules);
|
||||||
this.selectedStandards = this.domainRules[this.selectedDomain];
|
|
||||||
|
if (this.domainRules[this.selectedDomain] === undefined) {
|
||||||
|
this.selectedStandards = this.domainRules[this.defaultDomain];
|
||||||
|
} else {
|
||||||
|
this.selectedStandards = this.domainRules[this.selectedDomain];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setSelectedDomain: function (newDomain) {
|
setSelectedDomain: function (newDomain) {
|
||||||
|
@ -77,6 +176,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
window.WEB_API_MANAGER.stateLib = {
|
window.WEB_API_MANAGER.stateLib = {
|
||||||
generateStateObject
|
generateStateObject,
|
||||||
|
areRuleSetsIdentical,
|
||||||
|
areArrayValuesIdentical
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const rootObject = (window.browser || window.chrome);
|
const rootObject = window.browser || window.chrome;
|
||||||
const doc = window.document;
|
const doc = window.document;
|
||||||
const standards = window.WEB_API_MANAGER.standards;
|
const standards = window.WEB_API_MANAGER.standards;
|
||||||
const {storageLib, stateLib} = window.WEB_API_MANAGER;
|
const {storageLib, stateLib} = window.WEB_API_MANAGER;
|
||||||
|
@ -10,12 +10,14 @@
|
||||||
|
|
||||||
const state = stateLib.generateStateObject(defaultDomain, standards);
|
const state = stateLib.generateStateObject(defaultDomain, standards);
|
||||||
|
|
||||||
|
let globalVmInstance;
|
||||||
|
|
||||||
const onSettingsLoaded = function (storedSettings) {
|
const onSettingsLoaded = function (storedSettings) {
|
||||||
|
|
||||||
state.populateFromStorage(storedSettings);
|
state.populateFromStorage(storedSettings);
|
||||||
state.activeTab = "domain-rules";
|
state.activeTab = "domain-rules";
|
||||||
|
|
||||||
const vm = new Vue({
|
globalVmInstance = new Vue({
|
||||||
el: doc.querySelector("#config-root"),
|
el: doc.querySelector("#config-root"),
|
||||||
render: window.WEB_API_MANAGER.vueComponents["config-root"].render,
|
render: window.WEB_API_MANAGER.vueComponents["config-root"].render,
|
||||||
staticRenderFns: window.WEB_API_MANAGER.vueComponents["config-root"].staticRenderFns,
|
staticRenderFns: window.WEB_API_MANAGER.vueComponents["config-root"].staticRenderFns,
|
||||||
|
@ -36,12 +38,15 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
vm.$watch("selectedStandards", updateStoredSettings);
|
globalVmInstance.$watch("selectedStandards", updateStoredSettings);
|
||||||
vm.$watch("domainNames", updateStoredSettings);
|
globalVmInstance.$watch("domainNames", updateStoredSettings);
|
||||||
vm.$watch("shouldLog", updateStoredSettings);
|
globalVmInstance.$watch("shouldLog", updateStoredSettings);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
storageLib.get(onSettingsLoaded);
|
storageLib.get(onSettingsLoaded);
|
||||||
|
storageLib.onChange(function (newStoredValues) {
|
||||||
|
globalVmInstance.$data.setDomainRules(newStoredValues.domainRules);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -14,6 +14,12 @@
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
blockingRules: function () {
|
||||||
|
return this.$root.$data.domainsBlockingStandards();
|
||||||
|
},
|
||||||
|
allowingRules: function () {
|
||||||
|
return this.$root.$data.domainsBlockingNoStandards();
|
||||||
|
},
|
||||||
newDomainSubmitted: function () {
|
newDomainSubmitted: function () {
|
||||||
|
|
||||||
const state = this.$root.$data;
|
const state = this.$root.$data;
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
const logMessages = newDomainRules.map(function (newDomainRule) {
|
const logMessages = newDomainRules.map(function (newDomainRule) {
|
||||||
const {pattern, standards} = newDomainRule;
|
const {pattern, standards} = newDomainRule;
|
||||||
if (currentDomainRules[pattern] !== undefined && shouldOverwrite === false) {
|
if (currentDomainRules[pattern] !== undefined && shouldOverwrite === false) {
|
||||||
return ` ! ${pattern}: Skipped. Set to not override.\n`;
|
return ` ! ${pattern}: Skipped. Set to not override.\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
stateObject.setStandardsForDomain(pattern, standards);
|
stateObject.setStandardsForDomain(pattern, standards);
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
/**
|
/**
|
||||||
* Returns a new CSP instruction, with source with the given hash
|
* Returns a new CSP instruction, with source with the given hash
|
||||||
* whitelisted.
|
* whitelisted.
|
||||||
*
|
*
|
||||||
* If the CSP instruction has a "script-src" rule, then the hash-value
|
* If the CSP instruction has a "script-src" rule, then the hash-value
|
||||||
* will be inserted there. Otherwise, it will be inserted in the
|
* will be inserted there. Otherwise, it will be inserted in the
|
||||||
* default-src section.
|
* default-src section.
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes a buffer (such as a Uint8Array) to a base64 encoded string.
|
* Encodes a buffer (such as a Uint8Array) to a base64 encoded string.
|
||||||
*
|
*
|
||||||
* @param {ArrayBuffer} buf
|
* @param {ArrayBuffer} buf
|
||||||
* A buffer of binary data.
|
* A buffer of binary data.
|
||||||
*
|
*
|
||||||
|
|
|
@ -34,8 +34,39 @@
|
||||||
storageObject.sync.set(valueToStore, callback);
|
storageObject.sync.set(valueToStore, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChange = (function () {
|
||||||
|
|
||||||
|
const queue = [];
|
||||||
|
|
||||||
|
storageObject.onChanged.addListener(function (changes) {
|
||||||
|
|
||||||
|
if (changes[webApiManagerKeySettingsKey] === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {newValue, oldValue} = changes[webApiManagerKeySettingsKey];
|
||||||
|
|
||||||
|
if (JSON.stringify(newValue) === JSON.stringify(oldValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.forEach(function (callback) {
|
||||||
|
try {
|
||||||
|
callback(newValue);
|
||||||
|
} catch (e) {
|
||||||
|
// Intentionally left blank...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return function (callback) {
|
||||||
|
queue.push(callback);
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
|
||||||
window.WEB_API_MANAGER.storageLib = {
|
window.WEB_API_MANAGER.storageLib = {
|
||||||
get,
|
get,
|
||||||
set
|
set,
|
||||||
|
onChange
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
body, html {
|
body, html {
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
|
width: 640px;
|
||||||
}
|
}
|
||||||
|
|
||||||
section .row {
|
section .row {
|
||||||
|
@ -12,4 +13,4 @@ section .row {
|
||||||
|
|
||||||
.loaded .loading-section {
|
.loaded .loading-section {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,31 +5,131 @@
|
||||||
|
|
||||||
const rootObject = window.browser || window.chrome;
|
const rootObject = window.browser || window.chrome;
|
||||||
const doc = window.document;
|
const doc = window.document;
|
||||||
|
|
||||||
const configureButton = doc.getElementById("config-page-link");
|
const configureButton = doc.getElementById("config-page-link");
|
||||||
const listGroupElm = doc.querySelector("ul.list-group");
|
const domainRuleTableBody = doc.querySelector("#domain-rule-table tbody");
|
||||||
|
const defaultDomainRule = "(default)";
|
||||||
|
|
||||||
const addRuleToList = function (hostToRuleMapping, listElm, aHostName) {
|
/**
|
||||||
|
* Returns a function for use as the "onclick" handler for a toggle button.
|
||||||
|
*
|
||||||
|
* @param {string} hostName
|
||||||
|
* The name of the host to change the blocking settings for.
|
||||||
|
* @param {string} action
|
||||||
|
* Either "allow" (indicating that all APIs should be allowed for this
|
||||||
|
* host) or "block" (indicating that all APIs should be blocked for
|
||||||
|
* this host).
|
||||||
|
*
|
||||||
|
* @return {function}
|
||||||
|
* A function that takes a single event object as an argument. For
|
||||||
|
* use as an event handler callback.
|
||||||
|
*/
|
||||||
|
const createOnToggleHandler = function (hostName, action) {
|
||||||
|
const onClickHandler = function (event) {
|
||||||
|
const message = ["toggleBlocking", {
|
||||||
|
"action": action,
|
||||||
|
"hostName": hostName
|
||||||
|
}];
|
||||||
|
const button = event.target;
|
||||||
|
const containingRowElm = button.parentNode.parentNode;
|
||||||
|
|
||||||
const domainRule = hostToRuleMapping[aHostName];
|
const appliedRuleTd = containingRowElm.querySelector("td:nth-child(2)");
|
||||||
|
const numApisBlockedTd = containingRowElm.querySelector("td:nth-child(3)");
|
||||||
|
|
||||||
const liElm = doc.createElement("li");
|
button.className += " disabled";
|
||||||
liElm.className = "list-group-item";
|
button.innerHtml = "setting…";
|
||||||
|
|
||||||
if (domainRule !== "(default)") {
|
rootObject.runtime.sendMessage(message, function (responseMessage) {
|
||||||
liElm.className += " list-group-item-success";
|
|
||||||
|
const [messageType, numAPIsBlocked] = responseMessage;
|
||||||
|
|
||||||
|
if (messageType === "toggleBlockingResponse") {
|
||||||
|
|
||||||
|
numApisBlockedTd.innerText = numAPIsBlocked;
|
||||||
|
|
||||||
|
if (action === "block") {
|
||||||
|
appliedRuleTd.innerText = defaultDomainRule;
|
||||||
|
} else if (action === "allow") {
|
||||||
|
appliedRuleTd.innerText = hostName;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.innerText = "👍";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return onClickHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a TR element based on a domain's blocking status
|
||||||
|
*
|
||||||
|
* @param {string} domainName
|
||||||
|
* The name of a domain of a frame on the current tab
|
||||||
|
* @param {string} appliedRuleName
|
||||||
|
* The pattern matching rule for the rule set applied (or,
|
||||||
|
* if no matching rule "(default)").
|
||||||
|
* @param {number} numAPIsBlocked
|
||||||
|
* The number of APIs blocked for this domain.
|
||||||
|
*
|
||||||
|
* @return {Node}
|
||||||
|
* a HTMLTRElement object.
|
||||||
|
*/
|
||||||
|
const ruleToTr = function (domainName, appliedRuleName, numAPIsBlocked) {
|
||||||
|
|
||||||
|
const trElm = doc.createElement("tr");
|
||||||
|
|
||||||
|
const domainTd = doc.createElement("td");
|
||||||
|
const domainTdText = doc.createTextNode(domainName);
|
||||||
|
domainTd.appendChild(domainTdText);
|
||||||
|
trElm.appendChild(domainTd);
|
||||||
|
|
||||||
|
const ruleTd = doc.createElement("td");
|
||||||
|
ruleTd.appendChild(doc.createTextNode(appliedRuleName));
|
||||||
|
trElm.appendChild(ruleTd);
|
||||||
|
|
||||||
|
const numBlockedTd = doc.createElement("td");
|
||||||
|
numBlockedTd.appendChild(doc.createTextNode(numAPIsBlocked));
|
||||||
|
trElm.appendChild(numBlockedTd);
|
||||||
|
|
||||||
|
const actionsTd = doc.createElement("td");
|
||||||
|
const toggleButton = doc.createElement("button");
|
||||||
|
toggleButton.className = "btn btn-default btn-xs block-toggle";
|
||||||
|
|
||||||
|
const isAllowingAll = numAPIsBlocked === 0;
|
||||||
|
|
||||||
|
let toggleButtonText;
|
||||||
|
let toggleAction;
|
||||||
|
|
||||||
|
// If the domain is using the default rule, and the default rule is
|
||||||
|
// allowing all API's then do nothing, since there is no sensible
|
||||||
|
// option to "toggle" to.
|
||||||
|
if (isAllowingAll === false) {
|
||||||
|
toggleButtonText = "allow all";
|
||||||
|
toggleButton.className += " success";
|
||||||
|
toggleAction = "allow";
|
||||||
|
} else {
|
||||||
|
toggleButtonText = "remove grant";
|
||||||
|
toggleButton.className += " warn";
|
||||||
|
toggleAction = "block";
|
||||||
}
|
}
|
||||||
|
|
||||||
const spanElm = doc.createElement("span");
|
if (toggleButtonText !== undefined) {
|
||||||
spanElm.className = "badge";
|
const toggleButtonTextElm = doc.createTextNode(toggleButtonText);
|
||||||
|
toggleButton.appendChild(toggleButtonTextElm);
|
||||||
|
|
||||||
const badgeText = doc.createTextNode(domainRule);
|
if (toggleAction !== undefined) {
|
||||||
spanElm.appendChild(badgeText);
|
const onClickToggleButton = createOnToggleHandler(domainName, toggleAction);
|
||||||
liElm.appendChild(spanElm);
|
toggleButton.addEventListener("click", onClickToggleButton, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const textElm = doc.createTextNode(aHostName);
|
actionsTd.appendChild(toggleButton);
|
||||||
liElm.appendChild(textElm);
|
trElm.appendChild(actionsTd);
|
||||||
listElm.appendChild(liElm);
|
|
||||||
|
return trElm;
|
||||||
};
|
};
|
||||||
|
|
||||||
configureButton.addEventListener("click", function (event) {
|
configureButton.addEventListener("click", function (event) {
|
||||||
|
@ -52,10 +152,12 @@
|
||||||
|
|
||||||
doc.body.className = "loaded";
|
doc.body.className = "loaded";
|
||||||
|
|
||||||
const domainNames = Object.keys(response);
|
const currentDomains = Object.keys(response);
|
||||||
const addRuleToListBound = addRuleToList.bind(undefined, response, listGroupElm);
|
currentDomains.forEach(function (aDomain) {
|
||||||
|
const {ruleName, numRules} = response[aDomain];
|
||||||
domainNames.forEach(addRuleToListBound);
|
const rowElm = ruleToTr(aDomain, ruleName, numRules);
|
||||||
|
domainRuleTableBody.appendChild(rowElm);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,8 +27,19 @@
|
||||||
|
|
||||||
<div class="loaded-section">
|
<div class="loaded-section">
|
||||||
<p>The following origins are executing code on this page.</p>
|
<p>The following origins are executing code on this page.</p>
|
||||||
<ul class="list-group">
|
<table class="table table-striped" id="domain-rule-table">
|
||||||
</ul>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Domain</th>
|
||||||
|
<th>Rule</th>
|
||||||
|
<th># APIs Blocked</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" id="config-page-link" class="btn btn-default btn-block">
|
<button type="button" id="config-page-link" class="btn btn-default btn-block">
|
||||||
|
|
|
@ -28,7 +28,8 @@
|
||||||
"lint:fix": "node_modules/eslint/bin/eslint.js --fix .",
|
"lint:fix": "node_modules/eslint/bin/eslint.js --fix .",
|
||||||
"test": "npm run clean; npm run bundle && ln -s `ls dist/` dist/webapi_manager.zip && node_modules/cross-env/dist/bin/cross-env.js node_modules/mocha/bin/mocha test/unit/*.js test/functional/*.js --only-local-tests",
|
"test": "npm run clean; npm run bundle && ln -s `ls dist/` dist/webapi_manager.zip && node_modules/cross-env/dist/bin/cross-env.js node_modules/mocha/bin/mocha test/unit/*.js test/functional/*.js --only-local-tests",
|
||||||
"test:watch": "node_modules/cross-env/dist/bin/cross-env.js npm test --watch",
|
"test:watch": "node_modules/cross-env/dist/bin/cross-env.js npm test --watch",
|
||||||
"test:all": "npm run clean; npm run bundle && ln -s `ls dist/` dist/webapi_manager.zip && node_modules/cross-env/dist/bin/cross-env.js node_modules/mocha/bin/mocha test/unit/*.js test/functional/*.js"
|
"test:all": "npm run clean; npm run bundle && ln -s `ls dist/` dist/webapi_manager.zip && node_modules/cross-env/dist/bin/cross-env.js node_modules/mocha/bin/mocha test/unit/*.js test/functional/*.js",
|
||||||
|
"test:unit": "npm run clean; npm run bundle && ln -s `ls dist/` dist/webapi_manager.zip && node_modules/cross-env/dist/bin/cross-env.js node_modules/mocha/bin/mocha test/unit/*.js"
|
||||||
},
|
},
|
||||||
"pre-push": {
|
"pre-push": {
|
||||||
"run": [
|
"run": [
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<div class="domain-rules-container well">
|
<div class="domain-rules-container well">
|
||||||
<div class="radio" v-for="aDomain in domainNames">
|
<h3>Domains</h3>
|
||||||
|
<div class="radio" v-for="aDomain in blockingRules()">
|
||||||
<label>
|
<label>
|
||||||
<input type="radio"
|
<input type="radio"
|
||||||
:value="aDomain"
|
:value="aDomain"
|
||||||
|
@ -13,16 +14,31 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3>Whitelisted Domains</h3>
|
||||||
|
<div class="radio" v-for="aDomain in allowingRules()">
|
||||||
|
<label>
|
||||||
|
<input type="radio"
|
||||||
|
:value="aDomain"
|
||||||
|
v-model="selectedDomain"
|
||||||
|
@change="onRadioChange">
|
||||||
|
{{ aDomain }}
|
||||||
|
<span class="glyphicon glyphicon-remove"
|
||||||
|
v-if="!isDefault(aDomain)"
|
||||||
|
:data-domain="aDomain"
|
||||||
|
@click="onRemoveClick"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="alert alert-danger" role="alert" v-if="errorMessage">
|
<div class="alert alert-danger" role="alert" v-if="errorMessage">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" v-bind:class="{ 'has-error': errorMessage }">
|
<div class="form-group" v-bind:class="{ 'has-error': errorMessage }">
|
||||||
<label for="newDomainName">Add New Domain Rule</label>
|
<label for="newDomainName">Add New Domain Rule</label>
|
||||||
<input
|
<input class="form-control"
|
||||||
class="form-control"
|
v-model.trim="newDomain"
|
||||||
v-model.trim="newDomain"
|
placeholder="*.example.org">
|
||||||
placeholder="*.example.org">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
|
|
|
@ -6,6 +6,7 @@ const testServer = require("./lib/server");
|
||||||
const webdriver = require("selenium-webdriver");
|
const webdriver = require("selenium-webdriver");
|
||||||
const by = webdriver.By;
|
const by = webdriver.By;
|
||||||
const until = webdriver.until;
|
const until = webdriver.until;
|
||||||
|
const Context = require("selenium-webdriver/firefox").Context;
|
||||||
|
|
||||||
describe("Basic Functionality", function () {
|
describe("Basic Functionality", function () {
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ const promiseOpenImportExportTab = function (driver) {
|
||||||
|
|
||||||
describe("Import / Export", function () {
|
describe("Import / Export", function () {
|
||||||
|
|
||||||
this.timeout = () => 10000;
|
this.timeout = () => 20000;
|
||||||
|
|
||||||
describe("Exporting", function () {
|
describe("Exporting", function () {
|
||||||
|
|
||||||
|
@ -35,10 +35,14 @@ describe("Import / Export", function () {
|
||||||
.then(() => driverReference.findElement(by.css(".export-section select option:nth-child(1)")).click())
|
.then(() => driverReference.findElement(by.css(".export-section select option:nth-child(1)")).click())
|
||||||
.then(() => driverReference.findElement(by.css(".export-section textarea")).getAttribute("value"))
|
.then(() => driverReference.findElement(by.css(".export-section textarea")).getAttribute("value"))
|
||||||
.then(function (exportValue) {
|
.then(function (exportValue) {
|
||||||
|
driverReference.quit();
|
||||||
assert.equal(exportValue.trim(), emptyRuleSet, "Exported ruleset does not match expected value.");
|
assert.equal(exportValue.trim(), emptyRuleSet, "Exported ruleset does not match expected value.");
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
.catch(done);
|
.catch(function (e) {
|
||||||
|
driverReference.quit();
|
||||||
|
done(e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Exporting SVG and Beacon blocking rules", function (done) {
|
it("Exporting SVG and Beacon blocking rules", function (done) {
|
||||||
|
@ -55,9 +59,13 @@ describe("Import / Export", function () {
|
||||||
.then(() => driverReference.findElement(by.css(".export-section textarea")).getAttribute("value"))
|
.then(() => driverReference.findElement(by.css(".export-section textarea")).getAttribute("value"))
|
||||||
.then(function (exportValue) {
|
.then(function (exportValue) {
|
||||||
assert.equal(exportValue.trim(), blockingSVGandBeacon, "Exported ruleset does not match expected value.");
|
assert.equal(exportValue.trim(), blockingSVGandBeacon, "Exported ruleset does not match expected value.");
|
||||||
|
driverReference.quit();
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
.catch(done);
|
.catch(function (e) {
|
||||||
|
driverReference.quit();
|
||||||
|
done(e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -89,10 +97,13 @@ describe("Import / Export", function () {
|
||||||
})
|
})
|
||||||
.then(function (secondCheckboxValue) {
|
.then(function (secondCheckboxValue) {
|
||||||
assert.equal(secondCheckboxValue, utils.constants.svgBlockRule[0], "The second blocked standard should be the SVG standard.");
|
assert.equal(secondCheckboxValue, utils.constants.svgBlockRule[0], "The second blocked standard should be the SVG standard.");
|
||||||
driverReference.close();
|
driverReference.quit();
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
.catch(done);
|
.catch(function (e) {
|
||||||
|
driverReference.quit();
|
||||||
|
done(e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Importing rules for new domain", function (done) {
|
it("Importing rules for new domain", function (done) {
|
||||||
|
@ -112,7 +123,7 @@ describe("Import / Export", function () {
|
||||||
.then(() => driverReference.findElements(by.css("#domain-rules input[type='radio']")))
|
.then(() => driverReference.findElements(by.css("#domain-rules input[type='radio']")))
|
||||||
.then(function (radioElms) {
|
.then(function (radioElms) {
|
||||||
assert.equal(radioElms.length, 2, "There should be two domain rules in place.");
|
assert.equal(radioElms.length, 2, "There should be two domain rules in place.");
|
||||||
return radioElms[1].click();
|
return radioElms[0].click();
|
||||||
})
|
})
|
||||||
.then(() => driverReference.findElements(by.css("#domain-rules input[type='checkbox']:checked")))
|
.then(() => driverReference.findElements(by.css("#domain-rules input[type='checkbox']:checked")))
|
||||||
.then(function (checkboxElms) {
|
.then(function (checkboxElms) {
|
||||||
|
@ -126,10 +137,13 @@ describe("Import / Export", function () {
|
||||||
})
|
})
|
||||||
.then(function (secondCheckboxValue) {
|
.then(function (secondCheckboxValue) {
|
||||||
assert.equal(secondCheckboxValue, "WebGL Specification", "The second blocked standard should be 'WebGL Specification'.");
|
assert.equal(secondCheckboxValue, "WebGL Specification", "The second blocked standard should be 'WebGL Specification'.");
|
||||||
driverReference.close();
|
driverReference.quit();
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
.catch(done);
|
.catch(function (e) {
|
||||||
|
driverReference.quit();
|
||||||
|
done(e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,11 +66,6 @@ module.exports.promiseExtensionConfigPage = function (driver) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.promiseAddonConfigButton = function (driver) {
|
|
||||||
driver.setContext(Context.CHROME);
|
|
||||||
return driver.wait(until.elementLocated(by.id("config-page-link")), 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.promiseSetBlockingRules = function (driver, standardsToBlock) {
|
module.exports.promiseSetBlockingRules = function (driver, standardsToBlock) {
|
||||||
const setStandardsScript = injectedScripts.setStandardsAsBlockedScript(standardsToBlock);
|
const setStandardsScript = injectedScripts.setStandardsAsBlockedScript(standardsToBlock);
|
||||||
driver.setContext(Context.CONTENT);
|
driver.setContext(Context.CONTENT);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Tests to ensure that the pattern matching code (what determins which
|
* Tests to ensure that the pattern matching code (what determins which
|
||||||
* standard blocking rules should be applied to which domain) is correct.
|
* standard blocking rules should be applied to which domain) is correct.
|
||||||
*
|
*
|
||||||
* The code being tested here mostly lives in (from the project root)
|
* The code being tested here mostly lives in (from the project root)
|
||||||
* add-on/lib/domainmatcher.js
|
* add-on/lib/domainmatcher.js
|
||||||
*/
|
*/
|
||||||
|
|
126
test/unit/state-management.js
Normal file
126
test/unit/state-management.js
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
/**
|
||||||
|
* Tests to ensure that the tools that are used to manage the state
|
||||||
|
* of the extension perform as expected..
|
||||||
|
*
|
||||||
|
* The code being tested here mostly lives in (from the project root)
|
||||||
|
* add-on/config/js/state.js
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const assert = require("assert");
|
||||||
|
const path = require("path");
|
||||||
|
const addonLibPath = path.join(__dirname, "..", "..", "add-on", "lib");
|
||||||
|
const addonConfigLibPath = path.join(__dirname, "..", "..", "add-on", "config", "js");
|
||||||
|
|
||||||
|
// These will end up not returing anything, but will instead populate
|
||||||
|
// window.WEB_API_MANAGER
|
||||||
|
require(path.join(addonLibPath, "init.js"));
|
||||||
|
require(path.join(addonConfigLibPath, "state.js"));
|
||||||
|
const stateLib = window.WEB_API_MANAGER.stateLib;
|
||||||
|
|
||||||
|
describe("Extension State Management", function () {
|
||||||
|
|
||||||
|
describe("Array comparisons", function () {
|
||||||
|
|
||||||
|
it("Identical values in same order", function (done) {
|
||||||
|
|
||||||
|
const firstArray = [1, 2, 3, "A", "B", "C"];
|
||||||
|
const secondArray = [1, 2, 3, "A", "B", "C"];
|
||||||
|
const areEqual = stateLib.areArrayValuesIdentical(firstArray, secondArray);
|
||||||
|
|
||||||
|
assert.equal(areEqual, true, "Arrays contain identical values, so should evaluate to identical.");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Identical values in different order", function (done) {
|
||||||
|
|
||||||
|
const firstArray = [1, 2, 3, "A", "B", "C"];
|
||||||
|
const secondArray = [1, "B", 2, "A", 3, "C"];
|
||||||
|
const areEqual = stateLib.areArrayValuesIdentical(firstArray, secondArray);
|
||||||
|
|
||||||
|
assert.equal(areEqual, true, "Arrays contain identical values, so should evaluate to identical.");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Different values", function (done) {
|
||||||
|
|
||||||
|
const firstArray = [1, 2, 3, "A", "B", "C"];
|
||||||
|
const secondArray = ["Totally", "different", "values"];
|
||||||
|
const areEqual = stateLib.areArrayValuesIdentical(firstArray, secondArray);
|
||||||
|
|
||||||
|
assert.equal(areEqual, false, "Arrays contain different values, so should evaluate to not identical.");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Domain rule comparisons", function () {
|
||||||
|
|
||||||
|
it("Identical rule sets: same order", function (done) {
|
||||||
|
|
||||||
|
const firstRuleSet = {
|
||||||
|
"(default)": [],
|
||||||
|
"www.example.com": ["Beacon", "Vibrate API"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const secondRuleSet = {
|
||||||
|
"(default)": [],
|
||||||
|
"www.example.com": ["Beacon", "Vibrate API"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const areEqual = stateLib.areRuleSetsIdentical(firstRuleSet, secondRuleSet);
|
||||||
|
assert.equal(areEqual, false, "Both rule sets block the same standards on the same domains.");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Identical rule sets: different orders", function (done) {
|
||||||
|
|
||||||
|
const firstRuleSet = {
|
||||||
|
"(default)": [],
|
||||||
|
"www.example.com": ["Beacon", "Vibrate API"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const secondRuleSet = {
|
||||||
|
"www.example.com": ["Vibrate API", "Beacon"],
|
||||||
|
"(default)": []
|
||||||
|
};
|
||||||
|
|
||||||
|
const areEqual = stateLib.areRuleSetsIdentical(firstRuleSet, secondRuleSet);
|
||||||
|
assert.equal(areEqual, false, "Both rule sets block the same standards on the same domains.");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Different rule sets: different domains", function (done) {
|
||||||
|
|
||||||
|
const firstRuleSet = {
|
||||||
|
"(default)": [],
|
||||||
|
"www.example.com": ["Beacon", "Vibrate API"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const secondRuleSet = {
|
||||||
|
"(default)": [],
|
||||||
|
"www.example.net": ["Beacon", "Vibrate API"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const areEqual = stateLib.areRuleSetsIdentical(firstRuleSet, secondRuleSet);
|
||||||
|
assert.equal(areEqual, false, "The domains being described by these rule sets are different.");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Different rule sets: different standards", function (done) {
|
||||||
|
|
||||||
|
const firstRuleSet = {
|
||||||
|
"(default)": [],
|
||||||
|
"www.example.com": ["Beacon", "Vibrate API"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const secondRuleSet = {
|
||||||
|
"(default)": [],
|
||||||
|
"www.example.com": ["Beacon", "Gamepad API"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const areEqual = stateLib.areRuleSetsIdentical(firstRuleSet, secondRuleSet);
|
||||||
|
assert.equal(areEqual, false, "The standards blocked by the rule sets are different.");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue