diff --git a/src/bg/main.js b/src/bg/main.js
index 7be57b7..5b9f77e 100644
--- a/src/bg/main.js
+++ b/src/bg/main.js
@@ -145,6 +145,7 @@
sync: ns.sync,
unrestrictedTab: ns.unrestrictedTabs.has(tabId),
tabId,
+ xssBlockedInTab: XSS.getBlockedInTab(tabId),
});
},
diff --git a/src/ui/options.html b/src/ui/options.html
index 51bac06..716c275 100644
--- a/src/ui/options.html
+++ b/src/ui/options.html
@@ -109,8 +109,9 @@
(__MSG_XssFaq__)
-
+
+
diff --git a/src/ui/options.js b/src/ui/options.js
index 0eafab7..c99ff7f 100644
--- a/src/ui/options.js
+++ b/src/ui/options.js
@@ -108,16 +108,6 @@
url: a.href
});
}
- let button = document.querySelector("#btn-delete-xss-choices");
- let choices = UI.xssUserChoices;
- button.disabled = !choices || Object.keys(choices).length === 0;
- button.onclick = () => {
- UI.updateSettings({
- xssUserChoices: {}
- });
- button.disabled = true
- };
-
}
opt("clearclick");
diff --git a/src/ui/popup.html b/src/ui/popup.html
index 7b916d0..f64a261 100644
--- a/src/ui/popup.html
+++ b/src/ui/popup.html
@@ -45,6 +45,8 @@
>__MSG_OptOverrideTorBrowserPolicy____MSG_OptIncognitoPerm__
+
+
diff --git a/src/ui/ui.css b/src/ui/ui.css
index 6002c39..05da66b 100644
--- a/src/ui/ui.css
+++ b/src/ui/ui.css
@@ -519,4 +519,26 @@ legend {
.hilite-end .url {
transform: none;
transition: 1s transform;
-}
\ No newline at end of file
+}
+
+#xssChoices {
+ padding: .5em;
+ display: none;
+ flex-direction: column;
+}
+
+#xssChoices.populated {
+ display: flex;
+}
+
+#xssChoices option {
+ background: white;
+}
+
+#xssChoices option.block {
+ color: #a00;
+}
+
+#xssChoices option.allow {
+ color: #080;
+}
diff --git a/src/ui/ui.js b/src/ui/ui.js
index 216d139..b0e6fd3 100644
--- a/src/ui/ui.js
+++ b/src/ui/ui.js
@@ -39,6 +39,7 @@ var UI = (() => {
UI.seen = m.seen;
UI.unrestrictedTab = m.unrestrictedTab;
UI.xssUserChoices = m.xssUserChoices;
+ UI.xssBlockedInTab = m.xssBlockedInTab;
UI.local = m.local;
UI.sync = m.sync;
UI.forceIncognito = UI.incognito && !UI.sync.overrideTorBrowserPolicy;
@@ -53,6 +54,7 @@ var UI = (() => {
}
resolve();
if (UI.onSettings) UI.onSettings();
+ if (UI.tabId === -1 || UI.xssBlockedInTab) UI.createXSSChoiceManager();
await HighContrast.init();
}
});
@@ -128,6 +130,61 @@ var UI = (() => {
}
}
return input;
+ },
+
+ createXSSChoiceManager(parent = "#xssChoices") {
+ let choicesUI = document.querySelector(parent);
+ if (!choicesUI) return;
+ choicesUI.classList.remove("populated");
+ let choices = Object.entries(UI.xssUserChoices);
+ let choiceKeys = UI.xssBlockedInTab;
+ if (choiceKeys) {
+ choices = choices.filter(([key,])=> choiceKeys.includes(key));
+ }
+ if (!choices || Object.keys(choices).length === 0) {
+ return;
+ }
+
+ choicesUI.classList.add("populated");
+
+ choices.sort((a, b) => {
+ let x = a.join("|"), y = b.join("|");
+ return x < y ? -1 : x > y ? 1 : 0;
+ });
+ let list = choicesUI.querySelector("select") || choicesUI.appendChild(document.createElement("select"));
+ list.size = Math.min(choices.length, 6);
+ list.multiple = true;
+ for (let o of list.options) {
+ list.remove(o);
+ }
+ for (let [originKey, choice] of choices) {
+ let [source, destOrigin] = originKey.split(">");
+ let opt = document.createElement("option");
+ opt.className = choice;
+ opt.value = originKey;
+ let block = choice === "block";
+ opt.defaultSelected = block;
+ opt.text = _(`XSS_optAlways${block ? "Block" : "Allow"}`, [source || "[...]", destOrigin]);
+ list.add(opt);
+ }
+ let button = choicesUI.querySelector("button");
+ if (!button) {
+ button = choicesUI.appendChild(document.createElement("button"));
+ button.textContent = _("XSS_clearUserChoices");
+ }
+ button.onclick = () => {
+ let xssUserChoices = UI.xssUserChoices;
+ for (let o of list.selectedOptions) {
+ delete xssUserChoices[o.value];
+ list.remove(o);
+ }
+ if (list.options.length === 0) {
+ choicesUI.classList.remove("populated");
+ }
+ UI.updateSettings({
+ xssUserChoices
+ });
+ };
}
};
diff --git a/src/xss/InjectionCheckWorker.js b/src/xss/InjectionCheckWorker.js
index c78bbe9..e4d75c6 100644
--- a/src/xss/InjectionCheckWorker.js
+++ b/src/xss/InjectionCheckWorker.js
@@ -19,7 +19,7 @@ include("InjectionChecker.js");
let Handlers = {
async check({xssReq, skip}) {
- let {destUrl, unparsedRequest: request, debugging} = xssReq;
+ let {destUrl, request, debugging} = xssReq;
let {
skipParams,
skipRx
diff --git a/src/xss/XSS.js b/src/xss/XSS.js
index 0670586..ca0f315 100644
--- a/src/xss/XSS.js
+++ b/src/xss/XSS.js
@@ -6,19 +6,38 @@ var XSS = (() => {
let workersMap = new Map();
let promptsMap = new Map();
+ let blockedTabs = new Map();
let requestIdCount = 0;
async function getUserResponse(xssReq) {
- let {originKey} = xssReq;
+ let {originKey, request} = xssReq;
+ let {tabId, frameId} = request;
+ let {browserAction} = browser;
+ if (frameId === 0) {
+ if (blockedTabs.has(tabId)) {
+ blockedTabs.delete(tabId);
+ if ("setBadgeText" in browserAction) {
+ browserAction.setBadgeText({tabId, text: ""});
+ }
+ }
+ }
await promptsMap.get(originKey);
- // promptsMap.delete(originKey);
+
switch (await XSS.getUserChoice(originKey)) {
case "allow":
return ALLOW;
case "block":
log("Blocking request from %s to %s by previous XSS prompt user choice",
xssReq.srcUrl, xssReq.destUrl);
+
+ if ("setBadgeText" in browserAction) {
+ browserAction.setBadgeText({tabId, text: "XSS"});
+ browserAction.setBadgeBackgroundColor({tabId, color: [0, 0, 128, 160]});
+ }
+ let keys = blockedTabs.get(tabId);
+ if (!keys) blockedTabs.set(tabId, keys = new Set());
+ keys.add(originKey);
return ABORT;
}
return null;
@@ -215,7 +234,7 @@ var XSS = (() => {
let isGet = method === "GET";
return {
- unparsedRequest: request,
+ request,
srcUrl,
destUrl,
srcObj,
@@ -247,6 +266,10 @@ var XSS = (() => {
return this._userChoices[originKey];
},
+ getBlockedInTab(tabId) {
+ return blockedTabs.has(tabId) ? [...blockedTabs.get(tabId)] : null;
+ },
+
async maybe(xssReq) { // return reason or null if everything seems fine
if (await this.Exceptions.shouldIgnore(xssReq)) {
return null;
@@ -254,7 +277,7 @@ var XSS = (() => {
let skip = this.Exceptions.partial(xssReq);
let worker = new Worker(browser.runtime.getURL("/xss/InjectionCheckWorker.js"));
- let {requestId} = xssReq.unparsedRequest;
+ let {requestId} = xssReq.request;
workersMap.set(requestId, worker)
return await new Promise((resolve, reject) => {
worker.onmessage = e => {
@@ -282,7 +305,7 @@ var XSS = (() => {
let onNavError = details => {
debug("Navigation error: %o", details);
let {tabId, frameId, url} = details;
- let r = xssReq.unparsedRequest;
+ let r = xssReq.request;
if (tabId === r.tabId && frameId === r.frameId) {
cleanup();
reject(new Error("Timing: request interrupted while being filtered, no need to go on."));