diff --git a/src/img/error64.png b/src/img/error64.png
index 0400d353..4b22b1e3 100644
Binary files a/src/img/error64.png and b/src/img/error64.png differ
diff --git a/src/img/icon256.png b/src/img/icon256.png
index a4e2684b..cc5d9808 100644
Binary files a/src/img/icon256.png and b/src/img/icon256.png differ
diff --git a/src/img/icon48.png b/src/img/icon48.png
index 310fbdd2..06ef9fae 100644
Binary files a/src/img/icon48.png and b/src/img/icon48.png differ
diff --git a/src/img/icon96.png b/src/img/icon96.png
index de0b65cb..b7d2db36 100644
Binary files a/src/img/icon96.png and b/src/img/icon96.png differ
diff --git a/src/img/noscript-options.png b/src/img/noscript-options.png
index 5fa1020f..9ba17a85 100644
Binary files a/src/img/noscript-options.png and b/src/img/noscript-options.png differ
diff --git a/src/img/ui-black64.png b/src/img/ui-black64.png
index 5164774c..dad8c5db 100644
Binary files a/src/img/ui-black64.png and b/src/img/ui-black64.png differ
diff --git a/src/img/ui-clock64.png b/src/img/ui-clock64.png
index 16a33743..b5c4f914 100644
Binary files a/src/img/ui-clock64.png and b/src/img/ui-clock64.png differ
diff --git a/src/img/ui-close64.png b/src/img/ui-close64.png
index 2e15a2f1..a5130de5 100644
Binary files a/src/img/ui-close64.png and b/src/img/ui-close64.png differ
diff --git a/src/img/ui-custom64.png b/src/img/ui-custom64.png
index 71335ee7..6c664ee7 100644
Binary files a/src/img/ui-custom64.png and b/src/img/ui-custom64.png differ
diff --git a/src/img/ui-global-no64.png b/src/img/ui-global-no64.png
index 40d7e699..9d7cffae 100644
Binary files a/src/img/ui-global-no64.png and b/src/img/ui-global-no64.png differ
diff --git a/src/img/ui-global64.png b/src/img/ui-global64.png
index cbf6c5b2..85c67ba0 100644
Binary files a/src/img/ui-global64.png and b/src/img/ui-global64.png differ
diff --git a/src/img/ui-maybe64.png b/src/img/ui-maybe64.png
index d3f06256..b8f24d02 100644
Binary files a/src/img/ui-maybe64.png and b/src/img/ui-maybe64.png differ
diff --git a/src/img/ui-no64.png b/src/img/ui-no64.png
index 9f12a645..9628c727 100644
Binary files a/src/img/ui-no64.png and b/src/img/ui-no64.png differ
diff --git a/src/img/ui-part64.png b/src/img/ui-part64.png
index 806ef156..a77cc3c7 100644
Binary files a/src/img/ui-part64.png and b/src/img/ui-part64.png differ
diff --git a/src/img/ui-revoke-temp64.png b/src/img/ui-revoke-temp64.png
index 215d0a75..8589cae3 100644
Binary files a/src/img/ui-revoke-temp64.png and b/src/img/ui-revoke-temp64.png differ
diff --git a/src/img/ui-sub64.png b/src/img/ui-sub64.png
index 9c2aaabf..2bd936d2 100644
Binary files a/src/img/ui-sub64.png and b/src/img/ui-sub64.png differ
diff --git a/src/img/ui-tab-no64.png b/src/img/ui-tab-no64.png
index 3384b2b9..bfbfaaed 100644
Binary files a/src/img/ui-tab-no64.png and b/src/img/ui-tab-no64.png differ
diff --git a/src/img/ui-tab64.png b/src/img/ui-tab64.png
index acbdace5..c1bed869 100644
Binary files a/src/img/ui-tab64.png and b/src/img/ui-tab64.png differ
diff --git a/src/img/ui-temp-all64.png b/src/img/ui-temp-all64.png
index a6aef868..6098ba73 100644
Binary files a/src/img/ui-temp-all64.png and b/src/img/ui-temp-all64.png differ
diff --git a/src/img/ui-temp64.png b/src/img/ui-temp64.png
index 461936c5..733f7395 100644
Binary files a/src/img/ui-temp64.png and b/src/img/ui-temp64.png differ
diff --git a/src/img/ui-yes64.png b/src/img/ui-yes64.png
index 69ad53d9..7435ddf5 100644
Binary files a/src/img/ui-yes64.png and b/src/img/ui-yes64.png differ
diff --git a/src/ui/Prompts.js b/src/ui/Prompts.js
index 03ea9eec..9ce0b2a4 100644
--- a/src/ui/Prompts.js
+++ b/src/ui/Prompts.js
@@ -1,101 +1,101 @@
-var Prompts = (() => {
-
-
- var promptData;
- var backlog = [];
- class WindowManager {
- async open(data) {
- promptData = data;
- this.close();
- this.currentWindow = await browser.windows.create({
- url: browser.extension.getURL("ui/prompt.html"),
- type: "panel",
- allowScriptsToClose: true,
- // titlePreface: "NoScript ",
- width: data.features.width,
- height: data.features.height,
- });
- }
- async close() {
- if (this.currentWindow) {
- try {
- await browser.windows.remove(this.currentWindow.id);
- } catch (e) {
- debug(e);
- }
- this.currentWindow = null;
- }
- }
-
- async focus() {
- if (this.currentWindow) {
- try {
- await browser.windows.update(this.currentWindow.id,
- {
- focused: true,
- }
- );
- } catch (e) {
- error(e, "Focusing popup window");
- }
- }
- }
- }
-
- var winMan = new WindowManager();
- var Prompts = {
- DEFAULTS: {
- title: "",
- message: "Proceed?",
- options: [],
- checks: [],
- buttons: [_("Ok"), _("Cancel")],
- multiple: "close", // or "queue", or "focus"
- width: 400,
- height: 300,
- },
- async prompt(features) {
- features = Object.assign({}, this.DEFAULTS, features || {});
- return new Promise((resolve, reject) => {
- let data = {
- features,
- result: {
- button: -1,
- checks: [],
- option: null,
- },
- done() {
- this.done = () => {};
- winMan.close();
- resolve(this.result);
- if (backlog.length) {
- winMan.open(backlog.shift());
- } else {
- promptData = null;
- }
- }
- };
- if (promptData) {
- backlog.push(data);
- switch(promptData.features.multiple) {
- case "focus":
- winMan.focus();
- case "queue":
- break;
- default:
- promptData.done();
- }
- } else {
- winMan.open(data);
- }
- });
- },
-
- get promptData() {
- return promptData;
- }
- }
-
- return Prompts;
-
-})();
+var Prompts = (() => {
+
+
+ var promptData;
+ var backlog = [];
+ class WindowManager {
+ async open(data) {
+ promptData = data;
+ this.close();
+ this.currentWindow = await browser.windows.create({
+ url: browser.extension.getURL("ui/prompt.html"),
+ type: "panel",
+ allowScriptsToClose: true,
+ // titlePreface: "NoScript ",
+ width: data.features.width,
+ height: data.features.height,
+ });
+ }
+ async close() {
+ if (this.currentWindow) {
+ try {
+ await browser.windows.remove(this.currentWindow.id);
+ } catch (e) {
+ debug(e);
+ }
+ this.currentWindow = null;
+ }
+ }
+
+ async focus() {
+ if (this.currentWindow) {
+ try {
+ await browser.windows.update(this.currentWindow.id,
+ {
+ focused: true,
+ }
+ );
+ } catch (e) {
+ error(e, "Focusing popup window");
+ }
+ }
+ }
+ }
+
+ var winMan = new WindowManager();
+ var Prompts = {
+ DEFAULTS: {
+ title: "",
+ message: "Proceed?",
+ options: [],
+ checks: [],
+ buttons: [_("Ok"), _("Cancel")],
+ multiple: "close", // or "queue", or "focus"
+ width: 400,
+ height: 300,
+ },
+ async prompt(features) {
+ features = Object.assign({}, this.DEFAULTS, features || {});
+ return new Promise((resolve, reject) => {
+ let data = {
+ features,
+ result: {
+ button: -1,
+ checks: [],
+ option: null,
+ },
+ done() {
+ this.done = () => {};
+ winMan.close();
+ resolve(this.result);
+ if (backlog.length) {
+ winMan.open(backlog.shift());
+ } else {
+ promptData = null;
+ }
+ }
+ };
+ if (promptData) {
+ backlog.push(data);
+ switch(promptData.features.multiple) {
+ case "focus":
+ winMan.focus();
+ case "queue":
+ break;
+ default:
+ promptData.done();
+ }
+ } else {
+ winMan.open(data);
+ }
+ });
+ },
+
+ get promptData() {
+ return promptData;
+ }
+ }
+
+ return Prompts;
+
+})();
diff --git a/src/ui/options.css b/src/ui/options.css
index f7db24b1..7d62cd04 100644
--- a/src/ui/options.css
+++ b/src/ui/options.css
@@ -1,187 +1,184 @@
-
-/* @import url("chrome://browser/content/extension.css"); */
-body {
- background: #eee url("/img/noscript-options.png") no-repeat fixed top right;
- background-size: 8em;
- padding: 0 2em 0 0;
- margin: 0.5em 0.5em 0.5em 0.5em;
-}
-.mobile body {
- background-size: 4em;
- padding-right: 0;
-}
-
-
-#header {
- display: flex;
- flex-flow: column;
- padding: 0;
- margin: 0 6em 0 0;
- text-align: right;
-}
-#header h1 {
- color: #048;
- text-shadow: 0.06em 0.06em 0.06em rgba(0,0,0,.5);
- font-size: 2em;
- padding: 0;
- margin: 0;
- text-align: right;
-}
-#version {
- color: #048;
- font-size: 0.75em;
- padding: 0;
- margin: 0 0 0.5em;
- display: block;
- text-align: right;
-}
-
-.buttons {
- display: flex;
- flex-flow: row wrap;
- justify-content: flex-end;
- width: 100%;
- text-align: right;
-}
-
-#sect-general {
- display: flex;
- flex-direction: column;
- justify-content: space-around;
- font-size: 1em;
-}
-
-#sect-general label, #sect-general button, #sect-general span {
- white-space: nowrap;
-}
-
-.opt-group {
- display: flex;
- flex-flow: row wrap;
- justify-content: flex-start;
- border-bottom: 1px solid rgba(255, 255, 255, .5);
- padding: .5em 0;
-}
-
-.opt-group:last-child {
- border-bottom: none;
- margin-bottom: .5em;
-}
-
-section form, section fieldset {
- margin: .5em 0;
-}
-
-fieldset:disabled {
- opacity: .5;
-}
-
-.opt-group > span {
- margin: 0 .5em;
-}
-
-.sect-sites form {
- display: flex;
- align-items: baseline;
- flex-wrap: wrap;
- justify-content: space-between;
-}
-
-.sect-sites form > label {
- white-space: nowrap;
-}
-#newsite {
- flex: 2 2;
-}
-
-#policy {
- display: block;
- margin-top: .5em;
- min-height: 20em;
- width: 90%;
-}
-.hide, div.debug {
- display: none;
-}
-
-body.debug div.debug {
- display: initial;
-}
-
-.error {
- background: #ff8;
- color: red;
-}
-
-#policy-error {
- background: red;
- color: #ff8;
- padding: 0;
- margin: 0;
- font-weight: bold;
-}
-
-input, button {
- font-size: 1em;
-}
-
-button.add {
- font-weight: bold;
-}
-
-input[type="file"] {
- display: none;
-}
-
-.opt-group {
- padding: 0.5em 0;
-}
-#xssFaq {
- padding: 0.5em 1em;
-}
-#clearclick-options {
- display: none;
-}
-
-
-.flextabs__tab {
- /* shift all tabs to appear before content */
- order: -1;
- /* let tabs scale to fit multiple on each row */
- width: auto;
- margin: 0;
-}
-.flextabs__content--active {
- /* ignore states activated for multi (accordion) toggle view */
- display: none;
-}
-.flextabs__content--active--last {
- /* show the last activated item */
- display: block;
-}
-
-.flextabs__content, .flextabs__toggle[aria-expanded="true"] {
- background-color: rgba(200, 200, 200, .5) !important;
- border: 0 solid #888;
-}
-
-.flextabs__toggle {
- -moz-appearance: none;
- border-width: 0 1px 0 0 !important;
- margin: 0 4px 0 0;
- background: #ccc;
- outline-width: 1px 0 0 0 !important;
-}
-
-
-
-.flextabs__content {
- border-width: 0 1px 1px 0;
- border-radius: 0 .5em 0 0;
- padding: .5em;
-}
-
-.flextabs__toggle {
- border-radius: .2em .2em 0 0;
- padding: .2em .4em;
-}
+
+/* @import url("chrome://browser/content/extension.css"); */
+body {
+ background: #fff url("/img/noscript-options.png") no-repeat fixed top right;
+ background-size: 8em;
+ padding: 0 2em 0 0;
+ margin: 0.5em 0.5em 0.5em 0.5em;
+}
+.mobile body {
+ background-size: 4em;
+ padding-right: 0;
+}
+
+
+#header {
+ display: flex;
+ flex-flow: column;
+ padding: 0;
+ margin: 0 6em 0 0;
+ text-align: right;
+}
+#header h1 {
+ color: #848484;
+ font-size: 2em;
+ padding: 0;
+ margin: 0;
+ text-align: right;
+}
+#version {
+ color: #848484;
+ font-size: 0.75em;
+ padding: 0;
+ margin: 0 0 0.5em;
+ display: block;
+ text-align: right;
+}
+
+.buttons {
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: flex-end;
+ width: 100%;
+ text-align: right;
+}
+
+#sect-general {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ font-size: 1em;
+}
+
+#sect-general label, #sect-general button, #sect-general span {
+ white-space: nowrap;
+}
+
+.opt-group {
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: flex-start;
+ padding: .5em 0;
+}
+
+.opt-group:last-child {
+ border-bottom: none;
+ margin-bottom: .5em;
+}
+
+section form, section fieldset {
+ margin: .5em 0;
+}
+
+fieldset:disabled {
+ opacity: .5;
+}
+
+.opt-group > span {
+ margin: 0 .5em;
+}
+
+.sect-sites form {
+ display: flex;
+ align-items: baseline;
+ flex-wrap: wrap;
+ justify-content: space-between;
+}
+
+.sect-sites form > label {
+ white-space: nowrap;
+}
+#newsite {
+ flex: 2 2;
+}
+
+#policy {
+ display: block;
+ margin-top: .5em;
+ min-height: 20em;
+ width: 90%;
+}
+.hide, div.debug {
+ display: none;
+}
+
+body.debug div.debug {
+ display: initial;
+}
+
+.error {
+ background: #ff8;
+ color: red;
+}
+
+#policy-error {
+ background: red;
+ color: #ff8;
+ padding: 0;
+ margin: 0;
+ font-weight: bold;
+}
+
+input, button {
+}
+
+button.add {
+ font-weight: bold;
+}
+
+input[type="file"] {
+ display: none;
+}
+
+.opt-group {
+ padding: 0.5em 0;
+}
+#xssFaq {
+ padding: 0.5em 1em;
+}
+#clearclick-options {
+ display: none;
+}
+
+
+.flextabs__tab {
+ /* shift all tabs to appear before content */
+ order: -1;
+ /* let tabs scale to fit multiple on each row */
+ width: auto;
+ margin: 0;
+}
+.flextabs__content--active {
+ /* ignore states activated for multi (accordion) toggle view */
+ display: none;
+}
+.flextabs__content--active--last {
+ /* show the last activated item */
+ display: block;
+}
+
+.flextabs__content, .flextabs__toggle[aria-expanded="true"] {
+ background-color: rgba(230, 230, 230, .5) !important;
+ border: 0 solid #888;
+}
+
+.flextabs__toggle {
+ -moz-appearance: none;
+ border-width: 0 0 0 0 !important;
+ margin: 0 4px 0 0;
+ background: #e6e6e6;
+ outline-width: 1px 0 0 0 !important;
+}
+
+
+
+.flextabs__content {
+ border-width: 0 0 0 0;
+ border-radius: 0 .5em .5em .5em;
+ padding: .5em;
+}
+
+.flextabs__toggle {
+ border-radius: .2em .2em 0 0;
+ padding: .2em .4em;
+}
diff --git a/src/ui/options.html b/src/ui/options.html
index 6e2ad0e8..4c810c0e 100644
--- a/src/ui/options.html
+++ b/src/ui/options.html
@@ -1,129 +1,129 @@
-
-
-
-
-NoScript Settings
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- (__MSG_XssFaq__)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+NoScript Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (__MSG_XssFaq__)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ui/options.js b/src/ui/options.js
index 31cf5c3c..3d4ba3fa 100644
--- a/src/ui/options.js
+++ b/src/ui/options.js
@@ -1,199 +1,199 @@
-'use strict';
-(async () => {
-
- await UI.init();
-
- let policy = UI.policy;
-
- let version = browser.runtime.getManifest().version;
- document.querySelector("#version").textContent = _("Version", version);
- // simple general options
-
- let opt = UI.wireOption;
-
- opt("global", o => {
- if (o) {
- policy.enforced = !o.checked;
- UI.updateSettings({policy});
- }
- let {enforced} = policy;
- let disabled = !enforced;
- for (let e of document.querySelectorAll(".enforcement_required")) {
- e.disabled = disabled;
- }
- return disabled;
- });
-
- opt("auto", o => {
- if (o) {
- policy.autoAllowTop = o.checked;
- UI.updateSettings({policy});
- }
- return policy.autoAllowTop;
- });
-
- opt("xss");
- {
- let button = document.querySelector("#btn-reset");
- button.onclick = async () => {
- if (confirm(_("reset_warning"))) {
- policy = new Policy();
- await UI.updateSettings({policy, local: null, sync: null, xssUserChoices: {}});
- window.location.reload();
- }
- }
-
- let fileInput = document.querySelector("#file-import");
- fileInput.onchange = () => {
- let fr = new FileReader();
- fr.onload = async () => {
- try {
- await UI.importSettings(fr.result);
- } catch (e) {
- error(e, "Importing settings %s", fr.result);
- }
- location.reload();
- }
- fr.readAsText(fileInput.files[0]);
- }
-
- button = document.querySelector("#btn-import");
- button.onclick = () => fileInput.click();
-
- document.querySelector("#btn-export").addEventListener("click", async e => {
- let button = e.target;
- button.disabled = true;
- let settings = await UI.exportSettings();
- let f = document.createElement("iframe");
- f.srcdoc = `NoScript Export`;
- f.style.position = "fixed";
- f.style.top = "-999px";
- f.style.height = "1px";
- f.onload = () => {
- let w = f.contentWindow;
- let a = w.document.querySelector("a");
- a.href = w.URL.createObjectURL(new w.Blob([settings], {
- type: "text/plain"
- }));
- a.click();
- setTimeout(() => {
- f.remove();
- button.disabled = false;
- }, 1000);
-
- };
- document.body.appendChild(f);
- });
- }
-
- {
- let a = document.querySelector("#xssFaq a");
- a.onclick = e => {
- e.preventDefault();
- browser.tabs.create({
- url: a.href
- });
- }
- let button = document.querySelector("#btn-delete-xss-choices");
- let choices = UI.xssUserChoices;
- button.disabled = Object.keys(choices).length === 0;
- button.onclick = () => {
- UI.updateSettings({
- xssUserChoices: {}
- });
- button.disabled = true
- };
-
- }
-
- opt("clearclick");
- opt("debug", "local", b => {
- document.body.classList.toggle("debug", b);
- if (b) updateRawPolicyEditor();
- });
-
- // Appearance
-
- opt("showCountBadge", "local");
- opt("showCtxMenuItem", "local");
- opt("showFullAddresses", "local");
-
- // PRESET CUSTOMIZER
- {
- let parent = document.getElementById("presets");
- let presetsUI = new UI.Sites(parent,
- {"DEFAULT": true, "TRUSTED": true, "UNTRUSTED": true});
-
- presetsUI.render([""]);
- window.setTimeout(() => {
- let def = parent.querySelector('input.preset[value="DEFAULT"]');
- def.checked = true;
- def.click();
- }, 10);
- }
-
- // SITES UI
- let sitesUI = new UI.Sites(document.getElementById("sites"));
- {
- sitesUI.onChange = () => {
- if (UI.local.debug) {
- updateRawPolicyEditor();
- }
- };
- let sites = policy.sites;
- sitesUI.render(sites);
-
- let newSiteForm = document.querySelector("#form-newsite");
- let newSiteInput = newSiteForm.newsite;
- let button = newSiteForm.querySelector("button");
- let canAdd = s => policy.get(s).siteMatch === null;
-
- let validate = () => {
- let site = newSiteInput.value.trim();
- button.disabled = !(Sites.isValid(site) && canAdd(site));
- sitesUI.filterSites(site);
- }
- validate();
- newSiteInput.addEventListener("input", validate);
-
- newSiteForm.addEventListener("submit", e => {
- e.preventDefault();
- e.stopPropagation();
- let site = newSiteInput.value.trim();
- let valid = Sites.isValid(site);
- if (valid && canAdd(site)) {
- policy.set(site, policy.TRUSTED);
- UI.updateSettings({policy});
- newSiteInput.value = "";
- sitesUI.render(policy.sites);
- sitesUI.highlight(site);
- sitesUI.onChange();
- }
- }, true);
- }
-
-
- // UTILITY FUNCTIONS
-
- function updateRawPolicyEditor() {
- if (!UI.local.debug) return;
-
- // RAW POLICY EDITING (debug only)
- let policyEditor = document.getElementById("policy");
- policyEditor.value = JSON.stringify(policy.dry(true), null, 2);
- if (!policyEditor.onchange) policyEditor.onchange = (e) => {
- let ed = e.currentTarget
- try {
- policy = new Policy(JSON.parse(ed.value));
- UI.updateSettings({policy});
- sitesUI.render(policy.sites);
- ed.className = "";
- document.getElementById("policy-error").textContent = "";
- } catch (e) {
- error(e);
- ed.className = "error";
- document.getElementById("policy-error").textContent = e.message;
- }
- }
- }
-})();
+'use strict';
+(async () => {
+
+ await UI.init();
+
+ let policy = UI.policy;
+
+ let version = browser.runtime.getManifest().version;
+ document.querySelector("#version").textContent = _("Version", version);
+ // simple general options
+
+ let opt = UI.wireOption;
+
+ opt("global", o => {
+ if (o) {
+ policy.enforced = !o.checked;
+ UI.updateSettings({policy});
+ }
+ let {enforced} = policy;
+ let disabled = !enforced;
+ for (let e of document.querySelectorAll(".enforcement_required")) {
+ e.disabled = disabled;
+ }
+ return disabled;
+ });
+
+ opt("auto", o => {
+ if (o) {
+ policy.autoAllowTop = o.checked;
+ UI.updateSettings({policy});
+ }
+ return policy.autoAllowTop;
+ });
+
+ opt("xss");
+ {
+ let button = document.querySelector("#btn-reset");
+ button.onclick = async () => {
+ if (confirm(_("reset_warning"))) {
+ policy = new Policy();
+ await UI.updateSettings({policy, local: null, sync: null, xssUserChoices: {}});
+ window.location.reload();
+ }
+ }
+
+ let fileInput = document.querySelector("#file-import");
+ fileInput.onchange = () => {
+ let fr = new FileReader();
+ fr.onload = async () => {
+ try {
+ await UI.importSettings(fr.result);
+ } catch (e) {
+ error(e, "Importing settings %s", fr.result);
+ }
+ location.reload();
+ }
+ fr.readAsText(fileInput.files[0]);
+ }
+
+ button = document.querySelector("#btn-import");
+ button.onclick = () => fileInput.click();
+
+ document.querySelector("#btn-export").addEventListener("click", async e => {
+ let button = e.target;
+ button.disabled = true;
+ let settings = await UI.exportSettings();
+ let f = document.createElement("iframe");
+ f.srcdoc = `NoScript Export`;
+ f.style.position = "fixed";
+ f.style.top = "-999px";
+ f.style.height = "1px";
+ f.onload = () => {
+ let w = f.contentWindow;
+ let a = w.document.querySelector("a");
+ a.href = w.URL.createObjectURL(new w.Blob([settings], {
+ type: "text/plain"
+ }));
+ a.click();
+ setTimeout(() => {
+ f.remove();
+ button.disabled = false;
+ }, 1000);
+
+ };
+ document.body.appendChild(f);
+ });
+ }
+
+ {
+ let a = document.querySelector("#xssFaq a");
+ a.onclick = e => {
+ e.preventDefault();
+ browser.tabs.create({
+ url: a.href
+ });
+ }
+ let button = document.querySelector("#btn-delete-xss-choices");
+ let choices = UI.xssUserChoices;
+ button.disabled = Object.keys(choices).length === 0;
+ button.onclick = () => {
+ UI.updateSettings({
+ xssUserChoices: {}
+ });
+ button.disabled = true
+ };
+
+ }
+
+ opt("clearclick");
+ opt("debug", "local", b => {
+ document.body.classList.toggle("debug", b);
+ if (b) updateRawPolicyEditor();
+ });
+
+ // Appearance
+
+ opt("showCountBadge", "local");
+ opt("showCtxMenuItem", "local");
+ opt("showFullAddresses", "local");
+
+ // PRESET CUSTOMIZER
+ {
+ let parent = document.getElementById("presets");
+ let presetsUI = new UI.Sites(parent,
+ {"DEFAULT": true, "TRUSTED": true, "UNTRUSTED": true});
+
+ presetsUI.render([""]);
+ window.setTimeout(() => {
+ let def = parent.querySelector('input.preset[value="DEFAULT"]');
+ def.checked = true;
+ def.click();
+ }, 10);
+ }
+
+ // SITES UI
+ let sitesUI = new UI.Sites(document.getElementById("sites"));
+ {
+ sitesUI.onChange = () => {
+ if (UI.local.debug) {
+ updateRawPolicyEditor();
+ }
+ };
+ let sites = policy.sites;
+ sitesUI.render(sites);
+
+ let newSiteForm = document.querySelector("#form-newsite");
+ let newSiteInput = newSiteForm.newsite;
+ let button = newSiteForm.querySelector("button");
+ let canAdd = s => policy.get(s).siteMatch === null;
+
+ let validate = () => {
+ let site = newSiteInput.value.trim();
+ button.disabled = !(Sites.isValid(site) && canAdd(site));
+ sitesUI.filterSites(site);
+ }
+ validate();
+ newSiteInput.addEventListener("input", validate);
+
+ newSiteForm.addEventListener("submit", e => {
+ e.preventDefault();
+ e.stopPropagation();
+ let site = newSiteInput.value.trim();
+ let valid = Sites.isValid(site);
+ if (valid && canAdd(site)) {
+ policy.set(site, policy.TRUSTED);
+ UI.updateSettings({policy});
+ newSiteInput.value = "";
+ sitesUI.render(policy.sites);
+ sitesUI.highlight(site);
+ sitesUI.onChange();
+ }
+ }, true);
+ }
+
+
+ // UTILITY FUNCTIONS
+
+ function updateRawPolicyEditor() {
+ if (!UI.local.debug) return;
+
+ // RAW POLICY EDITING (debug only)
+ let policyEditor = document.getElementById("policy");
+ policyEditor.value = JSON.stringify(policy.dry(true), null, 2);
+ if (!policyEditor.onchange) policyEditor.onchange = (e) => {
+ let ed = e.currentTarget
+ try {
+ policy = new Policy(JSON.parse(ed.value));
+ UI.updateSettings({policy});
+ sitesUI.render(policy.sites);
+ ed.className = "";
+ document.getElementById("policy-error").textContent = "";
+ } catch (e) {
+ error(e);
+ ed.className = "error";
+ document.getElementById("policy-error").textContent = e.message;
+ }
+ }
+ }
+})();
diff --git a/src/ui/popup.css b/src/ui/popup.css
index ad5e3d0c..93e4eac6 100644
--- a/src/ui/popup.css
+++ b/src/ui/popup.css
@@ -1,239 +1,239 @@
-body {
- background: white;
-}
-
-#top {
- font-size: 1em;
- position: relative;
- margin: 0;
- height: 2.4em;
- min-width: 18.75em;
- border-bottom: 0.06em solid #eee;
- display: flex;
- -moz-user-select: none;
-}
-
-
-#top a {
- appearance: none !important;
- -moz-appearance: none !important;
- width: 2em;
- height: 2em;
- margin: 0.25em;
- cursor: pointer;
- font-size: 1em;
- font-family: sans-serif;
- font-weight: bold;
- color: black;
- background: transparent no-repeat center;
- background-size: 100%;
- transform: unset;
- transition: all 0.3s;
- border: none;
- display: block;
-
- top: 0;
- padding: 0;
- text-align: left;
- vertical-align: middle;
- line-height: 1em;
-
-}
-
-#top > .spacer {
- flex-grow: 1;
- display: block;
- cursor: pointer;
-}
-
-
-
-#top > .hider.open ~ .spacer {
- display: none;
-}
-
-.hider {
- background: #ccc;
- box-shadow: inset 0 1px 3px #444;
- border-radius: 1em 1em 0 0;
- display: none;
- position: relative;
- margin: .25em 1.5em;
- padding: 0;
-
- height: 2em;
- overflow: hidden;
- opacity: .5;
-}
-
-
-
-.hider.open {
- display: flex;
- flex-grow: 1;
- opacity: 1;
- padding-left: 2em;
-}
-.hider:hover {
- opacity: 1;
-}
-.hider:not(.open):not(.empty) {
- display: block;
- text-align: right;
- line-height: 1em;
- overflow: hidden;
- width: 2em;
-}
-
-
-#top .hider .reveal {
- display: block;
- font-size: 1.2em;
- font-weight: bold;
- padding: 0;
- text-align: center;
- margin: 0;
-}
-
-.hider.open > .reveal {
- display: none !important;
-}
-
-.hider:not(.open) > :not(.reveal) {
- display: none !important;
-}
-
-.hider-label {
- position: absolute;
- z-index: 100;
- top: .5em;
- right: .5em;
- color: #222;
- text-align: right;
- vertical-align: middle;
- line-height: 100%;
- font-size: 1em;
- font-weight: bold;
- pointer-events: none;
- text-shadow: -2px 0 2px white, 2px 0 2px white;
-}
-
-.hider-close {
- -moz-appearance: none;
- appearance: none;
- color: black;
- background: transparent;
- padding: 0;
- border-radius: .2em;
- border: none;
- position: absolute;
- left: .2em;
- top: 0;
- font-size: 1.5em;
- font-weight: bold;
- z-index: 100;
- vertical-align: middle;
- padding: .2em;
-}
-
-.hider-close:hover, .reveal:hover {
- color: white !important;
- text-shadow: -2px 0 2px red, 2px 0 2px red;
-}
-
-.hider > .icon {
- opacity: .7;
- margin: 0 .25em;
- padding: 0;
-}
-
-#top > a:hover {
- transform: scale(1.2);
-}
-
-#top a.icon {
- text-indent: -500em;
- color: transparent;
-}
-
-
-#top #revoke-temp {
- background-image: url(/img/ui-revoke-temp64.png);
-}
-#top #temp-trust-page {
- background-image: url(/img/ui-temp-all64.png);
-}
-
-#top #enforce-tab {
- background-image: url(/img/ui-tab-no64.png);
-}
-#top #enforce-tab[aria-pressed="true"] {
- background-image: url(/img/ui-tab64.png);
-}
-
-#top #enforce {
- background-image: url(/img/ui-global-no64.png);
-}
-#top #enforce[aria-pressed="true"] {
- background-image: url(/img/ui-global64.png);
-}
-
-#top #options {
- background-image: url(/img/noscript-options.png);
-}
-#top #close {
- background-image: url(/img/ui-close64.png);
-}
-
-#top #reload {
- background-image: url(/img/ui-reload64.png);
-}
-
-#sites {
- margin: 0.5em 0.25em;
-}
-
-#content {
- text-align: center;
-}
-#buttons {
- text-align: center;
- margin: 0.5em;
- display: flex;
- justify-content: space-around;
-
-}
-#buttons button {
- flex-grow: 1;
- margin: .5em 2em;
-}
-
-.disabled .toggle.icon, .toggle.icon:disabled {
- opacity: .2;
- pointer-events: none;
-}
-
-#message {
- height: auto;
- margin: .5em;
- padding: .8em 0 0 2.5em;
- background-size: 2em;
- background-position: left top;
- background-repeat: no-repeat;
- min-height: 3em;
- transition: height .5s;
- font-size: 1.2em;
- vertical-align: middle;
-}
-#message.hidden {
- display: none;
- height: 0;
- min-height: 0;
- overflow: hidden;
-}
-.warning {
- background-image: url("/img/warning64.png");
-}
-.error {
- background-image: url("/img/error64.png");
-}
+body {
+ background: white;
+}
+
+#top {
+ font-size: 1em;
+ position: relative;
+ margin: 0;
+ height: 2.4em;
+ min-width: 18.75em;
+ border-bottom: 0.06em solid #eee;
+ display: flex;
+ -moz-user-select: none;
+}
+
+
+#top a {
+ appearance: none !important;
+ -moz-appearance: none !important;
+ width: 2em;
+ height: 2em;
+ margin: 0.25em;
+ cursor: pointer;
+ font-size: 1em;
+ font-family: sans-serif;
+ font-weight: bold;
+ color: black;
+ background: transparent no-repeat center;
+ background-size: 100%;
+ transform: unset;
+ transition: all 0.3s;
+ border: none;
+ display: block;
+
+ top: 0;
+ padding: 0;
+ text-align: left;
+ vertical-align: middle;
+ line-height: 1em;
+
+}
+
+#top > .spacer {
+ flex-grow: 1;
+ display: block;
+ cursor: pointer;
+}
+
+
+
+#top > .hider.open ~ .spacer {
+ display: none;
+}
+
+.hider {
+ background: #ccc;
+ box-shadow: inset 0 1px 3px #444;
+ border-radius: 1em 1em 0 0;
+ display: none;
+ position: relative;
+ margin: .25em 1.5em;
+ padding: 0;
+
+ height: 2em;
+ overflow: hidden;
+ opacity: .5;
+}
+
+
+
+.hider.open {
+ display: flex;
+ flex-grow: 1;
+ opacity: 1;
+ padding-left: 2em;
+}
+.hider:hover {
+ opacity: 1;
+}
+.hider:not(.open):not(.empty) {
+ display: block;
+ text-align: right;
+ line-height: 1em;
+ overflow: hidden;
+ width: 2em;
+}
+
+
+#top .hider .reveal {
+ display: block;
+ font-size: 1.2em;
+ font-weight: bold;
+ padding: 0;
+ text-align: center;
+ margin: 0;
+}
+
+.hider.open > .reveal {
+ display: none !important;
+}
+
+.hider:not(.open) > :not(.reveal) {
+ display: none !important;
+}
+
+.hider-label {
+ position: absolute;
+ z-index: 100;
+ top: .5em;
+ right: .5em;
+ color: #222;
+ text-align: right;
+ vertical-align: middle;
+ line-height: 100%;
+ font-size: 1em;
+ font-weight: bold;
+ pointer-events: none;
+ text-shadow: -2px 0 2px white, 2px 0 2px white;
+}
+
+.hider-close {
+ -moz-appearance: none;
+ appearance: none;
+ color: black;
+ background: transparent;
+ padding: 0;
+ border-radius: .2em;
+ border: none;
+ position: absolute;
+ left: .2em;
+ top: 0;
+ font-size: 1.5em;
+ font-weight: bold;
+ z-index: 100;
+ vertical-align: middle;
+ padding: .2em;
+}
+
+.hider-close:hover, .reveal:hover {
+ color: white !important;
+ text-shadow: -2px 0 2px red, 2px 0 2px red;
+}
+
+.hider > .icon {
+ opacity: .7;
+ margin: 0 .25em;
+ padding: 0;
+}
+
+#top > a:hover {
+ transform: scale(1.2);
+}
+
+#top a.icon {
+ text-indent: -500em;
+ color: transparent;
+}
+
+
+#top #revoke-temp {
+ background-image: url(/img/ui-revoke-temp64.png);
+}
+#top #temp-trust-page {
+ background-image: url(/img/ui-temp-all64.png);
+}
+
+#top #enforce-tab {
+ background-image: url(/img/ui-tab-no64.png);
+}
+#top #enforce-tab[aria-pressed="true"] {
+ background-image: url(/img/ui-tab64.png);
+}
+
+#top #enforce {
+ background-image: url(/img/ui-global-no64.png);
+}
+#top #enforce[aria-pressed="true"] {
+ background-image: url(/img/ui-global64.png);
+}
+
+#top #options {
+ background-image: url(/img/noscript-options.png);
+}
+#top #close {
+ background-image: url(/img/ui-close64.png);
+}
+
+#top #reload {
+ background-image: url(/img/ui-reload64.png);
+}
+
+#sites {
+ margin: 0.5em 0.25em;
+}
+
+#content {
+ text-align: center;
+}
+#buttons {
+ text-align: center;
+ margin: 0.5em;
+ display: flex;
+ justify-content: space-around;
+
+}
+#buttons button {
+ flex-grow: 1;
+ margin: .5em 2em;
+}
+
+.disabled .toggle.icon, .toggle.icon:disabled {
+ opacity: .2;
+ pointer-events: none;
+}
+
+#message {
+ height: auto;
+ margin: .5em;
+ padding: .8em 0 0 2.5em;
+ background-size: 2em;
+ background-position: left top;
+ background-repeat: no-repeat;
+ min-height: 3em;
+ transition: height .5s;
+ font-size: 1.2em;
+ vertical-align: middle;
+}
+#message.hidden {
+ display: none;
+ height: 0;
+ min-height: 0;
+ overflow: hidden;
+}
+.warning {
+ background-image: url("/img/warning64.png");
+}
+.error {
+ background-image: url("/img/error64.png");
+}
diff --git a/src/ui/popup.html b/src/ui/popup.html
index 517b233e..8b8c8931 100644
--- a/src/ui/popup.html
+++ b/src/ui/popup.html
@@ -1,47 +1,47 @@
-
-
-
-
-
-NoScript Settings
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+NoScript Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ui/popup.js b/src/ui/popup.js
index aed981b2..c5081e70 100644
--- a/src/ui/popup.js
+++ b/src/ui/popup.js
@@ -1,271 +1,271 @@
-'use strict';
-
-var sitesUI;
-
-addEventListener("unload", e => {
- if (!UI.initialized) {
- Messages.send("openStandalonePopup");
- }
-});
-
-(async () => {
-
- function showMessage(className, message) {
- let el = document.getElementById("message");
- el.textContent = message;
- el.className = className;
- }
-
- try {
- let tabId;
- let pendingReload = false;
- let isBrowserAction = true;
- let optionsClosed = false;
- let tab = (await browser.tabs.query({
- windowId: browser.windows ?
- (await browser.windows.getLastFocused()).id
- : null,
- active: true
- }))[0];
-
- if (!tab || tab.id === -1) {
- log("No tab found to open the UI for");
- close();
- }
- if (tab.url === document.URL) {
- isBrowserAction = false;
- try {
- tabId = parseInt(document.URL.match(/#.*\btab(\d+)/)[1]);
- } catch (e) {
- close();
- }
- addEventListener("blur", close);
- } else {
- tabId = tab.id;
- }
-
- await UI.init(tabId);
-
- if (isBrowserAction) {
- browser.tabs.onActivated.addListener(e => {
- if (e.tabId !== tabId) close();
- });
- }
-
- await include("/ui/toolbar.js");
- {
- let clickHandlers = {
- "options": e => {
- browser.runtime.openOptionsPage();
- close();
- },
- "close": close,
- "reload": reload,
- "temp-trust-page": e => sitesUI.tempTrustAll(),
- "revoke-temp": e => {
- UI.revokeTemp();
- close();
- }
- };
- for (let [id, handler] of Object.entries(clickHandlers)) {
- document.getElementById(id).onclick = handler;
- }
- }
- {
- let policy = UI.policy;
- let pressed = policy.enforced;
- let button = document.getElementById("enforce");
- button.setAttribute("aria-pressed", pressed);
- button.textContent = button.title = _(pressed ? "NoEnforcement" : "Enforce");
- button.onclick = async () => {
- this.disabled = true;
- policy.enforced = !pressed;
- await UI.updateSettings({policy, reloadAffected: true});
- close();
- }
- }
- {
- let pressed = !UI.unrestrictedTab;
- let button = document.getElementById("enforce-tab");
- button.setAttribute("aria-pressed", pressed);
- button.textContent = button.title = _(pressed ? "NoEnforcementForTab" : "EnforceForTab");
- if (UI.policy.enforced) {
- button.onclick = async () => {
- this.disabled = true;
- await UI.updateSettings({
- unrestrictedTab: pressed,
- reloadAffected: true,
- });
- close();
- }
- } else {
- button.disabled = true;
- }
- }
-
-
- let mainFrame = UI.seen && UI.seen.find(thing => thing.request.type === "main_frame");
- debug("Seen: %o", UI.seen);
- if (!mainFrame) {
- let isHttp = /^https?:/.test(tab.url);
- try {
- await browser.tabs.executeScript(tabId, { code: "" });
- if (isHttp) {
- document.body.classList.add("disabled");
- showMessage("warning", _("freshInstallReload"));
- let buttons = document.querySelector("#buttons");
- let b = document.createElement("button");
- b.textContent = _("OK");
- b.onclick = document.getElementById("reload").onclick = () => {
- reload();
- close();
- }
- buttons.appendChild(b);
- b = document.createElement("button");
- b.textContent = _("Cancel");
- b.onclick = () => close();
- buttons.appendChild(b);
- return;
- }
- } catch (e) {
- error(e, "Could not run scripts on %s: privileged page?", tab.url);
- }
-
- await include("/lib/restricted.js");
- let isRestricted = isRestrictedURL(tab.url);
- if (!isHttp || isRestricted) {
- showMessage("warning", _("privilegedPage"));
- let tempTrust = document.getElementById("temp-trust-page");
- tempTrust.disabled = true;
- return;
- }
- if (!UI.seen) {
- if (!isHttp) return;
- UI.seen = [
- mainFrame = {
- request: { url: tab.url, documentUrl: tab.url, type: "main_frame" }
- }
- ];
- }
- }
-
- let justDomains = !UI.local.showFullAddresses;
-
- sitesUI = new UI.Sites(document.getElementById("sites"));
-
- sitesUI.onChange = (row) => {
- pendingReload = !row.temp2perm;
- if (optionsClosed) return;
- browser.tabs.query({url: browser.runtime.getManifest().options_ui.page })
- .then(tabs => {
- browser.tabs.remove(tabs.map(t => t.id));
- });
- optionsClosed = true;
- };
- initSitesUI();
- UI.onSettings = initSitesUI;
-
-
-
- function initSitesUI() {
- pendingReload = false;
- let {
- typesMap
- } = sitesUI;
- typesMap.clear();
- let policySites = UI.policy.sites;
- let domains = new Map();
-
- function urlToLabel(url) {
- let origin = Sites.origin(url);
- let match = policySites.match(url);
- if (match) return match;
- if (domains.has(origin)) {
- if (justDomains) return domains.get(origin);
- } else {
- let domain = tld.getDomain(url.hostname);
- if (domain) {
- domain = url.protocol === "https:" ? Sites.secureDomainKey(domain) : domain;
- } else {
- domain = url.protocol;
- }
- domains.set(origin, domain);
- if (justDomains) return domain;
- }
- return origin;
- }
- let seen = UI.seen;
- let parsedSeen = seen.map(thing => Object.assign({
- type: thing.policyType
- }, Sites.parse(thing.request.url)))
- .filter(parsed => parsed.url && (
- parsed.url.origin !== "null" || parsed.url.protocol === "file:"));
-
- let sitesSet = new Set(
- parsedSeen.map(parsed => parsed.label = urlToLabel(parsed.url))
- );
- if (!justDomains) {
- for (let domain of domains.values()) sitesSet.add(domain);
- }
- let sites = [...sitesSet];
- for (let parsed of parsedSeen) {
- sites.filter(s => parsed.label === s || domains.get(Sites.origin(parsed.url)) === s).forEach(m => {
- let siteTypes = typesMap.get(m);
- if (!siteTypes) typesMap.set(m, siteTypes = new Set());
- siteTypes.add(parsed.type);
- });
- }
-
- sitesUI.mainUrl = new URL(mainFrame.request.url)
- sitesUI.mainSite = urlToLabel(sitesUI.mainUrl);
- sitesUI.mainDomain = tld.getDomain(sitesUI.mainUrl.hostname);
-
- sitesUI.render(sites);
- }
-
- function reload() {
- if (sitesUI) sitesUI.clear();
- browser.tabs.reload(tabId);
- pendingReload = false;
- }
-
- function close() {
- if (isBrowserAction) {
- window.close();
- } else {
- //browser.windows.remove(tab.windowId);
- browser.tabs.remove(tab.id);
- }
- }
-
- let {
- onCompleted
- } = browser.webNavigation;
-
- let loadSnapshot = sitesUI.snapshot;
- let onCompletedListener = navigated => {
- if (navigated.tabId === tabId) {
- UI.pullSettings();
- }
- };
- onCompleted.addListener(onCompletedListener, {
- url: [{
- hostContains: sitesUI.mainDomain
- }]
- });
- addEventListener("unload", e => {
- onCompleted.removeListener(onCompletedListener);
- debug("pendingReload", pendingReload);
- if (pendingReload) {
- UI.updateSettings({
- policy: UI.policy,
- reloadAffected: true,
- });
- }
- }, true);
- } catch (e) {
- error(e, "Can't open popup");
- close();
- }
-
-})();
+'use strict';
+
+var sitesUI;
+
+addEventListener("unload", e => {
+ if (!UI.initialized) {
+ Messages.send("openStandalonePopup");
+ }
+});
+
+(async () => {
+
+ function showMessage(className, message) {
+ let el = document.getElementById("message");
+ el.textContent = message;
+ el.className = className;
+ }
+
+ try {
+ let tabId;
+ let pendingReload = false;
+ let isBrowserAction = true;
+ let optionsClosed = false;
+ let tab = (await browser.tabs.query({
+ windowId: browser.windows ?
+ (await browser.windows.getLastFocused()).id
+ : null,
+ active: true
+ }))[0];
+
+ if (!tab || tab.id === -1) {
+ log("No tab found to open the UI for");
+ close();
+ }
+ if (tab.url === document.URL) {
+ isBrowserAction = false;
+ try {
+ tabId = parseInt(document.URL.match(/#.*\btab(\d+)/)[1]);
+ } catch (e) {
+ close();
+ }
+ addEventListener("blur", close);
+ } else {
+ tabId = tab.id;
+ }
+
+ await UI.init(tabId);
+
+ if (isBrowserAction) {
+ browser.tabs.onActivated.addListener(e => {
+ if (e.tabId !== tabId) close();
+ });
+ }
+
+ await include("/ui/toolbar.js");
+ {
+ let clickHandlers = {
+ "options": e => {
+ browser.runtime.openOptionsPage();
+ close();
+ },
+ "close": close,
+ "reload": reload,
+ "temp-trust-page": e => sitesUI.tempTrustAll(),
+ "revoke-temp": e => {
+ UI.revokeTemp();
+ close();
+ }
+ };
+ for (let [id, handler] of Object.entries(clickHandlers)) {
+ document.getElementById(id).onclick = handler;
+ }
+ }
+ {
+ let policy = UI.policy;
+ let pressed = policy.enforced;
+ let button = document.getElementById("enforce");
+ button.setAttribute("aria-pressed", pressed);
+ button.textContent = button.title = _(pressed ? "NoEnforcement" : "Enforce");
+ button.onclick = async () => {
+ this.disabled = true;
+ policy.enforced = !pressed;
+ await UI.updateSettings({policy, reloadAffected: true});
+ close();
+ }
+ }
+ {
+ let pressed = !UI.unrestrictedTab;
+ let button = document.getElementById("enforce-tab");
+ button.setAttribute("aria-pressed", pressed);
+ button.textContent = button.title = _(pressed ? "NoEnforcementForTab" : "EnforceForTab");
+ if (UI.policy.enforced) {
+ button.onclick = async () => {
+ this.disabled = true;
+ await UI.updateSettings({
+ unrestrictedTab: pressed,
+ reloadAffected: true,
+ });
+ close();
+ }
+ } else {
+ button.disabled = true;
+ }
+ }
+
+
+ let mainFrame = UI.seen && UI.seen.find(thing => thing.request.type === "main_frame");
+ debug("Seen: %o", UI.seen);
+ if (!mainFrame) {
+ let isHttp = /^https?:/.test(tab.url);
+ try {
+ await browser.tabs.executeScript(tabId, { code: "" });
+ if (isHttp) {
+ document.body.classList.add("disabled");
+ showMessage("warning", _("freshInstallReload"));
+ let buttons = document.querySelector("#buttons");
+ let b = document.createElement("button");
+ b.textContent = _("OK");
+ b.onclick = document.getElementById("reload").onclick = () => {
+ reload();
+ close();
+ }
+ buttons.appendChild(b);
+ b = document.createElement("button");
+ b.textContent = _("Cancel");
+ b.onclick = () => close();
+ buttons.appendChild(b);
+ return;
+ }
+ } catch (e) {
+ error(e, "Could not run scripts on %s: privileged page?", tab.url);
+ }
+
+ await include("/lib/restricted.js");
+ let isRestricted = isRestrictedURL(tab.url);
+ if (!isHttp || isRestricted) {
+ showMessage("warning", _("privilegedPage"));
+ let tempTrust = document.getElementById("temp-trust-page");
+ tempTrust.disabled = true;
+ return;
+ }
+ if (!UI.seen) {
+ if (!isHttp) return;
+ UI.seen = [
+ mainFrame = {
+ request: { url: tab.url, documentUrl: tab.url, type: "main_frame" }
+ }
+ ];
+ }
+ }
+
+ let justDomains = !UI.local.showFullAddresses;
+
+ sitesUI = new UI.Sites(document.getElementById("sites"));
+
+ sitesUI.onChange = (row) => {
+ pendingReload = !row.temp2perm;
+ if (optionsClosed) return;
+ browser.tabs.query({url: browser.runtime.getManifest().options_ui.page })
+ .then(tabs => {
+ browser.tabs.remove(tabs.map(t => t.id));
+ });
+ optionsClosed = true;
+ };
+ initSitesUI();
+ UI.onSettings = initSitesUI;
+
+
+
+ function initSitesUI() {
+ pendingReload = false;
+ let {
+ typesMap
+ } = sitesUI;
+ typesMap.clear();
+ let policySites = UI.policy.sites;
+ let domains = new Map();
+
+ function urlToLabel(url) {
+ let origin = Sites.origin(url);
+ let match = policySites.match(url);
+ if (match) return match;
+ if (domains.has(origin)) {
+ if (justDomains) return domains.get(origin);
+ } else {
+ let domain = tld.getDomain(url.hostname);
+ if (domain) {
+ domain = url.protocol === "https:" ? Sites.secureDomainKey(domain) : domain;
+ } else {
+ domain = url.protocol;
+ }
+ domains.set(origin, domain);
+ if (justDomains) return domain;
+ }
+ return origin;
+ }
+ let seen = UI.seen;
+ let parsedSeen = seen.map(thing => Object.assign({
+ type: thing.policyType
+ }, Sites.parse(thing.request.url)))
+ .filter(parsed => parsed.url && (
+ parsed.url.origin !== "null" || parsed.url.protocol === "file:"));
+
+ let sitesSet = new Set(
+ parsedSeen.map(parsed => parsed.label = urlToLabel(parsed.url))
+ );
+ if (!justDomains) {
+ for (let domain of domains.values()) sitesSet.add(domain);
+ }
+ let sites = [...sitesSet];
+ for (let parsed of parsedSeen) {
+ sites.filter(s => parsed.label === s || domains.get(Sites.origin(parsed.url)) === s).forEach(m => {
+ let siteTypes = typesMap.get(m);
+ if (!siteTypes) typesMap.set(m, siteTypes = new Set());
+ siteTypes.add(parsed.type);
+ });
+ }
+
+ sitesUI.mainUrl = new URL(mainFrame.request.url)
+ sitesUI.mainSite = urlToLabel(sitesUI.mainUrl);
+ sitesUI.mainDomain = tld.getDomain(sitesUI.mainUrl.hostname);
+
+ sitesUI.render(sites);
+ }
+
+ function reload() {
+ if (sitesUI) sitesUI.clear();
+ browser.tabs.reload(tabId);
+ pendingReload = false;
+ }
+
+ function close() {
+ if (isBrowserAction) {
+ window.close();
+ } else {
+ //browser.windows.remove(tab.windowId);
+ browser.tabs.remove(tab.id);
+ }
+ }
+
+ let {
+ onCompleted
+ } = browser.webNavigation;
+
+ let loadSnapshot = sitesUI.snapshot;
+ let onCompletedListener = navigated => {
+ if (navigated.tabId === tabId) {
+ UI.pullSettings();
+ }
+ };
+ onCompleted.addListener(onCompletedListener, {
+ url: [{
+ hostContains: sitesUI.mainDomain
+ }]
+ });
+ addEventListener("unload", e => {
+ onCompleted.removeListener(onCompletedListener);
+ debug("pendingReload", pendingReload);
+ if (pendingReload) {
+ UI.updateSettings({
+ policy: UI.policy,
+ reloadAffected: true,
+ });
+ }
+ }, true);
+ } catch (e) {
+ error(e, "Can't open popup");
+ close();
+ }
+
+})();
diff --git a/src/ui/prompt.css b/src/ui/prompt.css
index 9406f014..dd84e82a 100644
--- a/src/ui/prompt.css
+++ b/src/ui/prompt.css
@@ -1,91 +1,91 @@
-
-body {
- bottom: 8px;
- font-family: sans-serif;
- font-size: 12px;
- color: #222;
-}
-
-#header {
- text-align: left;
- margin: 0;
- line-height: 24px;
- color: #048;
- position: relative;
- font-size: 24px;
- z-index: 500;
- padding: 8px;
- display: block;
- background: url(/img/icon96.png) no-repeat top right;
- height: 96px;
-}
-
-#title {
- margin-right: 96px;
- font-size: 24px;
- position: absolute;
- bottom: 0;
- top: 0;
-}
-
-#main {
- background: linear-gradient(to bottom, #e4f5fc 0%,#bfe8f9 41%,#9fd8ef 90%,#2ab0ed 100%) no-repeat;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 120px 16px 16px 16px;
- top: 0;
- left: 0;
- right:0;
- bottom: 0;
- position: fixed;
- justify-content: center;
-}
-#message {
- flex-grow: 1;
- width: 100%;
- max-height: 300px;
- padding: 8px;
- text-align: center;
- word-break: break-all;
-}
-#message.multiline {
- overflow: auto;
- font-size: 12px;
- text-align: justify;
- margin-bottom: 16px;
- background: rgba(255,255,255,.5);
-}
-#message.multiline p {
- margin: 1px;
- padding: 0;
-}
-#options {
- display: flex;
- flex-grow: 2;
- flex-direction: column;
- text-align: left;
- align-items:baseline;
- justify-content: center;
-}
-
-
-#checks {
- display: flex;
- flex-direction: column;
- flex-grow: 1;
- text-align: left;
-}
-
-#buttons {
- width: 100%;
- display: flex;
- flex-grow: 0;
- flex-direction: row;
- align-items: center;
- margin: 8px;
- justify-content: space-around;
-}
-#buttons button {
- min-width: 100px;
-}
+
+body {
+ bottom: 8px;
+ font-family: sans-serif;
+ font-size: 12px;
+ color: #222;
+}
+
+#header {
+ text-align: left;
+ margin: 0;
+ line-height: 24px;
+ color: #048;
+ position: relative;
+ font-size: 24px;
+ z-index: 500;
+ padding: 8px;
+ display: block;
+ background: url(/img/icon96.png) no-repeat top right;
+ height: 96px;
+}
+
+#title {
+ margin-right: 96px;
+ font-size: 24px;
+ position: absolute;
+ bottom: 0;
+ top: 0;
+}
+
+#main {
+ background: linear-gradient(to bottom, #e4f5fc 0%,#bfe8f9 41%,#9fd8ef 90%,#2ab0ed 100%) no-repeat;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 120px 16px 16px 16px;
+ top: 0;
+ left: 0;
+ right:0;
+ bottom: 0;
+ position: fixed;
+ justify-content: center;
+}
+#message {
+ flex-grow: 1;
+ width: 100%;
+ max-height: 300px;
+ padding: 8px;
+ text-align: center;
+ word-break: break-all;
+}
+#message.multiline {
+ overflow: auto;
+ font-size: 12px;
+ text-align: justify;
+ margin-bottom: 16px;
+ background: rgba(255,255,255,.5);
+}
+#message.multiline p {
+ margin: 1px;
+ padding: 0;
+}
+#options {
+ display: flex;
+ flex-grow: 2;
+ flex-direction: column;
+ text-align: left;
+ align-items:baseline;
+ justify-content: center;
+}
+
+
+#checks {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ text-align: left;
+}
+
+#buttons {
+ width: 100%;
+ display: flex;
+ flex-grow: 0;
+ flex-direction: row;
+ align-items: center;
+ margin: 8px;
+ justify-content: space-around;
+}
+#buttons button {
+ min-width: 100px;
+}
diff --git a/src/ui/prompt.html b/src/ui/prompt.html
index 902b3758..b9048631 100644
--- a/src/ui/prompt.html
+++ b/src/ui/prompt.html
@@ -1,32 +1,32 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ui/prompt.js b/src/ui/prompt.js
index f19aac95..8bc80b09 100644
--- a/src/ui/prompt.js
+++ b/src/ui/prompt.js
@@ -1,91 +1,91 @@
-(async () => {
- window.bg = await browser.runtime.getBackgroundPage();
- ["Prompts"]
- .forEach(p => window[p] = bg[p]);
- let data = Prompts.promptData;
- debug(data);
- let {title, message, options, checks, buttons} = data.features;
-
- function labelFor(el, text) {
- let label = document.createElement("label");
- label.setAttribute("for", el.id);
- label.textContent = text;
- return label;
- }
-
- function createInput(container, {label, type, name, checked}, count) {
- let input = document.createElement("input");
- input.type = type;
- input.value = count;
- input.name = name;
- input.checked = checked;
- input.id = `${name}-${count}`;
- let sub = document.createElement("div");
- sub.appendChild(input);
- sub.appendChild(labelFor(input, label));
- container.appendChild(sub);
- }
-
- function createButton(container, label, count) {
- let button = document.createElement("button");
- if (count === 0) button.type = "submit";
- button.id = `${button}-${count}`;
- button.value = count;
- button.textContent = label;
- container.appendChild(button);
- }
-
- function renderInputs(container, dataset, type, name) {
- if (typeof container === "string") {
- container = document.querySelector(container);
- }
- if (typeof dataset === "string") {
- container.innerHTML = dataset;
- return;
- }
- container.innerHTML = "";
- let count = 0;
- if (dataset && dataset[Symbol.iterator]) {
- let create = type === "button" ? createButton : createInput;
- for (let data of dataset) {
- data.type = type;
- data.name = name;
- create(container, data, count++);
- }
- }
- }
- if (title) {
- document.title = title;
- document.querySelector("#title").textContent = title;
- }
- if (message) {
- let lines = message.split(/\n/);
- let container = document.querySelector("#message");
- container.classList.toggle("multiline", lines.length > 1);
- message.innerHTML = "";
- for (let l of lines) {
- let p = document.createElement("p");
- p.textContent = l;
- container.appendChild(p);
- }
- }
- renderInputs("#options", options, "radio", "opt");
- renderInputs("#checks", checks, "checkbox", "flag");
- renderInputs("#buttons", buttons, "button", "button");
- addEventListener("unload", e => {
- data.done();
- });
-
- let buttonClicked = e => {
- let {result} = data;
- result.button = parseInt(e.currentTarget.value);
- let option = document.querySelector('#options [type="radio"]:checked');
- result.option = option && parseInt(option.value);
- result.checks = [...document.querySelectorAll('#checks [type="checkbox"]:checked')]
- .map(c => parseInt(c.value));
- data.done();
- };
- for (let b of document.querySelectorAll("#buttons button")) {
- b.addEventListener("click", buttonClicked);
- }
-})();
+(async () => {
+ window.bg = await browser.runtime.getBackgroundPage();
+ ["Prompts"]
+ .forEach(p => window[p] = bg[p]);
+ let data = Prompts.promptData;
+ debug(data);
+ let {title, message, options, checks, buttons} = data.features;
+
+ function labelFor(el, text) {
+ let label = document.createElement("label");
+ label.setAttribute("for", el.id);
+ label.textContent = text;
+ return label;
+ }
+
+ function createInput(container, {label, type, name, checked}, count) {
+ let input = document.createElement("input");
+ input.type = type;
+ input.value = count;
+ input.name = name;
+ input.checked = checked;
+ input.id = `${name}-${count}`;
+ let sub = document.createElement("div");
+ sub.appendChild(input);
+ sub.appendChild(labelFor(input, label));
+ container.appendChild(sub);
+ }
+
+ function createButton(container, label, count) {
+ let button = document.createElement("button");
+ if (count === 0) button.type = "submit";
+ button.id = `${button}-${count}`;
+ button.value = count;
+ button.textContent = label;
+ container.appendChild(button);
+ }
+
+ function renderInputs(container, dataset, type, name) {
+ if (typeof container === "string") {
+ container = document.querySelector(container);
+ }
+ if (typeof dataset === "string") {
+ container.innerHTML = dataset;
+ return;
+ }
+ container.innerHTML = "";
+ let count = 0;
+ if (dataset && dataset[Symbol.iterator]) {
+ let create = type === "button" ? createButton : createInput;
+ for (let data of dataset) {
+ data.type = type;
+ data.name = name;
+ create(container, data, count++);
+ }
+ }
+ }
+ if (title) {
+ document.title = title;
+ document.querySelector("#title").textContent = title;
+ }
+ if (message) {
+ let lines = message.split(/\n/);
+ let container = document.querySelector("#message");
+ container.classList.toggle("multiline", lines.length > 1);
+ message.innerHTML = "";
+ for (let l of lines) {
+ let p = document.createElement("p");
+ p.textContent = l;
+ container.appendChild(p);
+ }
+ }
+ renderInputs("#options", options, "radio", "opt");
+ renderInputs("#checks", checks, "checkbox", "flag");
+ renderInputs("#buttons", buttons, "button", "button");
+ addEventListener("unload", e => {
+ data.done();
+ });
+
+ let buttonClicked = e => {
+ let {result} = data;
+ result.button = parseInt(e.currentTarget.value);
+ let option = document.querySelector('#options [type="radio"]:checked');
+ result.option = option && parseInt(option.value);
+ result.checks = [...document.querySelectorAll('#checks [type="checkbox"]:checked')]
+ .map(c => parseInt(c.value));
+ data.done();
+ };
+ for (let b of document.querySelectorAll("#buttons button")) {
+ b.addEventListener("click", buttonClicked);
+ }
+})();
diff --git a/src/ui/resize_hack.js b/src/ui/resize_hack.js
index c981e282..a7a3be09 100644
--- a/src/ui/resize_hack.js
+++ b/src/ui/resize_hack.js
@@ -1,15 +1,15 @@
-document.addEventListener("DOMContentLoaded", async e => {
- // Fix for Fx57 bug where bundled page loaded using
- // browser.windows.create won't show contents unless resized.
- // See https://bugzilla.mozilla.org/show_bug.cgi?id=1402110
- let win = await browser.windows.getCurrent({populate: true});
- if (win.tabs[0].url === document.URL) {
- debug("Resize hack");
- await browser.windows.update(win.id, {
- width: win.width + 1
- });
- await browser.windows.update(win.id, {
- width: win.width
- });
- }
-});
+document.addEventListener("DOMContentLoaded", async e => {
+ // Fix for Fx57 bug where bundled page loaded using
+ // browser.windows.create won't show contents unless resized.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1402110
+ let win = await browser.windows.getCurrent({populate: true});
+ if (win.tabs[0].url === document.URL) {
+ debug("Resize hack");
+ await browser.windows.update(win.id, {
+ width: win.width + 1
+ });
+ await browser.windows.update(win.id, {
+ width: win.width
+ });
+ }
+});
diff --git a/src/ui/siteInfo.html b/src/ui/siteInfo.html
index 0cb24ec0..dcd2443e 100644
--- a/src/ui/siteInfo.html
+++ b/src/ui/siteInfo.html
@@ -1,5 +1,5 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/src/ui/siteInfo.js b/src/ui/siteInfo.js
index 708c9830..43113700 100644
--- a/src/ui/siteInfo.js
+++ b/src/ui/siteInfo.js
@@ -1,20 +1,20 @@
-(async () => {
- let [domain, tabId] = decodeURIComponent(location.hash.replace("#", "")).split(";");
- const BASE = "https://noscript.net";
- await include(['/lib/punycode.js', '/common/Storage.js']);
- let {siteInfoConsent} = await Storage.get("sync", "siteInfoConsent");
- if (!siteInfoConsent) {
- await include('/common/locale.js');
- siteInfoConsent = confirm(_("siteInfo_confirm", [domain, BASE]));
- if (siteInfoConsent) {
- await Storage.set("sync", {siteInfoConsent});
- } else {
- let current = await browser.tabs.getCurrent();
- await browser.tabs.update(parseInt(tabId), {active: true});
- await browser.tabs.remove(current.id);
- return;
- }
- }
- let ace = punycode.toASCII(domain);
- location.href = `${BASE}/about/${domain};${ace}`;
-})();
+(async () => {
+ let [domain, tabId] = decodeURIComponent(location.hash.replace("#", "")).split(";");
+ const BASE = "https://noscript.net";
+ await include(['/lib/punycode.js', '/common/Storage.js']);
+ let {siteInfoConsent} = await Storage.get("sync", "siteInfoConsent");
+ if (!siteInfoConsent) {
+ await include('/common/locale.js');
+ siteInfoConsent = confirm(_("siteInfo_confirm", [domain, BASE]));
+ if (siteInfoConsent) {
+ await Storage.set("sync", {siteInfoConsent});
+ } else {
+ let current = await browser.tabs.getCurrent();
+ await browser.tabs.update(parseInt(tabId), {active: true});
+ await browser.tabs.remove(current.id);
+ return;
+ }
+ }
+ let ace = punycode.toASCII(domain);
+ location.href = `${BASE}/about/${domain};${ace}`;
+})();
diff --git a/src/ui/toolbar.js b/src/ui/toolbar.js
index d2a2f6e9..2ee1c1ff 100644
--- a/src/ui/toolbar.js
+++ b/src/ui/toolbar.js
@@ -1,117 +1,117 @@
-{
- let toolbar = document.getElementById("top");
- let spacer = toolbar.querySelector(".spacer");
- let hider = toolbar.querySelector(".hider");
-
- if (UI.local.toolbarLayout) {
- debug(uneval(UI.local.toolbarLayout));
- let {left, right, hidden} = UI.local.toolbarLayout;
- for (let id of left) {
- toolbar.insertBefore(document.getElementById(id), hider);
- }
- for (let id of right) {
- toolbar.appendChild(document.getElementById(id));
- }
- for (let id of hidden) {
- hider.appendChild(document.getElementById(id));
- }
- }
-
- for (let i of toolbar.querySelectorAll(".icon")) {
- if (!i.title) i.title = i.textContent;
- }
-
- function toggleHider(b) {
- let cl = hider.classList;
- cl.toggle("open", b);
- cl.toggle("empty", !hider.querySelector(".icon"));
- }
- hider.querySelector(".hider-close").onclick = e => {
- toggleHider(false);
- };
-
- toggleHider(false);
-
- let dnd = {
- dragstart(ev) {
- let d = ev.target;
- if (hider.querySelectorAll(".icon").length) {
- toggleHider(true);
- }
-
- if (!d.classList.contains("icon")) {
- ev.preventDefault();
- return;
- }
- d.style.opacity = ".5";
- let dt = ev.dataTransfer;
- dt.setData("text/plain", d.id);
- dt.dropEffect = "move";
- dt.setDragImage(d, 0, 0);
- toggleHider(true);
- },
- dragend(ev) {
- ev.target.style.opacity = "";
- },
- dragover(ev) {
- ev.preventDefault();
- },
- dragenter(ev) {
- let t = ev.target;
- },
- dragleave(ev) {
- let t = ev.target;
- },
- drop(ev) {
- let t = ev.target;
- let d = document.getElementById(ev.dataTransfer.getData("text/plain"));
- switch(t) {
- case hider:
- t.appendChild(d);
- break;
- case toolbar:
- t.insertBefore(d, ev.clientX < hider.offsetLeft ? hider : spacer.nextElementSibling);
- break;
- default:
- t.parentNode.insertBefore(d, ev.clientX < (t.offsetLeft + t.offsetWidth) ? t : t.nextElementSibling);
- }
-
- let left = [], right = [];
- let side = left;
- for (let el of document.querySelectorAll("#top > .icon, #top > .spacer")) {
- if (el === spacer) {
- side = right;
- } else {
- side.push(el.id);
- }
- }
- UI.local.toolbarLayout = {
- left, right,
- hidden: Array.map(document.querySelectorAll("#top > .hider > .icon"), el => el.id),
- };
-
- debug("%o", UI.local);
- UI.updateSettings({local: UI.local});
- },
-
- click(ev) {
- let el = ev.target;
- if (el.parentNode === hider && el.classList.contains("icon")) {
- ev.preventDefault();
- ev.stopPropagation();
- } else if (el === spacer || el.classList.contains("reveal")) {
- toggleHider(true);
- }
- }
-
- };
-
-
- for (let [action, handler] of Object.entries(dnd)) {
- toolbar.addEventListener(action, handler, true);
- }
-
- for (let draggable of document.querySelectorAll("#top .icon")) {
- draggable.setAttribute("draggable", "true");
- }
-}
+{
+ let toolbar = document.getElementById("top");
+ let spacer = toolbar.querySelector(".spacer");
+ let hider = toolbar.querySelector(".hider");
+
+ if (UI.local.toolbarLayout) {
+ debug(uneval(UI.local.toolbarLayout));
+ let {left, right, hidden} = UI.local.toolbarLayout;
+ for (let id of left) {
+ toolbar.insertBefore(document.getElementById(id), hider);
+ }
+ for (let id of right) {
+ toolbar.appendChild(document.getElementById(id));
+ }
+ for (let id of hidden) {
+ hider.appendChild(document.getElementById(id));
+ }
+ }
+
+ for (let i of toolbar.querySelectorAll(".icon")) {
+ if (!i.title) i.title = i.textContent;
+ }
+
+ function toggleHider(b) {
+ let cl = hider.classList;
+ cl.toggle("open", b);
+ cl.toggle("empty", !hider.querySelector(".icon"));
+ }
+ hider.querySelector(".hider-close").onclick = e => {
+ toggleHider(false);
+ };
+
+ toggleHider(false);
+
+ let dnd = {
+ dragstart(ev) {
+ let d = ev.target;
+ if (hider.querySelectorAll(".icon").length) {
+ toggleHider(true);
+ }
+
+ if (!d.classList.contains("icon")) {
+ ev.preventDefault();
+ return;
+ }
+ d.style.opacity = ".5";
+ let dt = ev.dataTransfer;
+ dt.setData("text/plain", d.id);
+ dt.dropEffect = "move";
+ dt.setDragImage(d, 0, 0);
+ toggleHider(true);
+ },
+ dragend(ev) {
+ ev.target.style.opacity = "";
+ },
+ dragover(ev) {
+ ev.preventDefault();
+ },
+ dragenter(ev) {
+ let t = ev.target;
+ },
+ dragleave(ev) {
+ let t = ev.target;
+ },
+ drop(ev) {
+ let t = ev.target;
+ let d = document.getElementById(ev.dataTransfer.getData("text/plain"));
+ switch(t) {
+ case hider:
+ t.appendChild(d);
+ break;
+ case toolbar:
+ t.insertBefore(d, ev.clientX < hider.offsetLeft ? hider : spacer.nextElementSibling);
+ break;
+ default:
+ t.parentNode.insertBefore(d, ev.clientX < (t.offsetLeft + t.offsetWidth) ? t : t.nextElementSibling);
+ }
+
+ let left = [], right = [];
+ let side = left;
+ for (let el of document.querySelectorAll("#top > .icon, #top > .spacer")) {
+ if (el === spacer) {
+ side = right;
+ } else {
+ side.push(el.id);
+ }
+ }
+ UI.local.toolbarLayout = {
+ left, right,
+ hidden: Array.map(document.querySelectorAll("#top > .hider > .icon"), el => el.id),
+ };
+
+ debug("%o", UI.local);
+ UI.updateSettings({local: UI.local});
+ },
+
+ click(ev) {
+ let el = ev.target;
+ if (el.parentNode === hider && el.classList.contains("icon")) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ } else if (el === spacer || el.classList.contains("reveal")) {
+ toggleHider(true);
+ }
+ }
+
+ };
+
+
+ for (let [action, handler] of Object.entries(dnd)) {
+ toolbar.addEventListener(action, handler, true);
+ }
+
+ for (let draggable of document.querySelectorAll("#top .icon")) {
+ draggable.setAttribute("draggable", "true");
+ }
+}
diff --git a/src/ui/ui-hc.css b/src/ui/ui-hc.css
index e1c19443..d82143eb 100644
--- a/src/ui/ui-hc.css
+++ b/src/ui/ui-hc.css
@@ -1,67 +1,67 @@
-input {
- transform: none !important;
- width: auto !important;
- position: static !important;
-}
-
-input[type="radio"] {
- -moz-appearance: radio !important;
- padding-right: .2em !important;
-}
-input[type="checkbox"] {
- -moz-appearance: checkbox !important;
-}
-
-
-button {
- text-indent: 0 !important;
-}
-
-label {
- display: initial !important;
- position: static !important;
- transform: none !important;
- opacity: 1 !important;
- text-indent: 0 !Important;
- position: static;
- width: auto !important;
- padding: 4px !important;
-}
-
-span.preset {
- display: block;
- width: auto !important;
- white-space: nowrap !important;
-}
-
-input.temp {
- position: static !important;
- opacity: 1 !important;
-}
-
-.full-address {
- font-size: 130%;
-}
-
-tr.site {
- border-top: 1px solid #888;
-}
-
-#top {
- display:flex;
- flex-flow: row;
- justify-content: space-around;
-
-}
-#top button {
- position: static;
- width: auto;
-}
-#top button.icon {
- font-size: 12px !important;
- font-family: arial sans-serif !important;
-}
-
-#noscript-popup #high-contrast-chooser {
- display: block;
-}
+input {
+ transform: none !important;
+ width: auto !important;
+ position: static !important;
+}
+
+input[type="radio"] {
+ -moz-appearance: radio !important;
+ padding-right: .2em !important;
+}
+input[type="checkbox"] {
+ -moz-appearance: checkbox !important;
+}
+
+
+button {
+ text-indent: 0 !important;
+}
+
+label {
+ display: initial !important;
+ position: static !important;
+ transform: none !important;
+ opacity: 1 !important;
+ text-indent: 0 !Important;
+ position: static;
+ width: auto !important;
+ padding: 4px !important;
+}
+
+span.preset {
+ display: block;
+ width: auto !important;
+ white-space: nowrap !important;
+}
+
+input.temp {
+ position: static !important;
+ opacity: 1 !important;
+}
+
+.full-address {
+ font-size: 130%;
+}
+
+tr.site {
+ border-top: 1px solid #888;
+}
+
+#top {
+ display:flex;
+ flex-flow: row;
+ justify-content: space-around;
+
+}
+#top button {
+ position: static;
+ width: auto;
+}
+#top button.icon {
+ font-size: 12px !important;
+ font-family: arial sans-serif !important;
+}
+
+#noscript-popup #high-contrast-chooser {
+ display: block;
+}
diff --git a/src/ui/ui.css b/src/ui/ui.css
index 0d86ddc7..87c1d1fa 100644
--- a/src/ui/ui.css
+++ b/src/ui/ui.css
@@ -1,415 +1,415 @@
-
-body {
- font-family: sans-serif;
- font: -moz-use-system-font;
- font-size: 12px;
-}
-
-.mobile > body {
- font-size: 4mm;
- min-width: auto;
-}
-
-.mobile .desktop {
- display: none !important;
-}
-
- @media (max-width: 100mm) {
- body {
- background-size: 4em !important;
- padding-right: 0 !important;
- }
-
- .presets {
- width: 0;
- }
-
- .presets input.preset {
- min-width: 0 !important;
- background-color: none !important;
- margin-bottom: 0;
- margin-top: 1mm;
- font-weight: bold;
- }
- .presets input.temp {
- position: static;
- }
- .presets label.preset {
- font-size: 50%;
- top: -1mm;
- left: 0;
- margin: 0;
- padding: 0;
- text-align: center;
- text-shadow: 0 0 4px #ff8;
- position: absolute;
- overflow: visible;
- }
-
- td.presets {
- white-space: nowrap !important;
- vertical-align: bottom;
- }
- .url {
- white-space: wrap;
- word-break: break-all;
- font-size: 75%;
- letter-spacing: -0.2mm;
-
- }
-
- }
-
-input[type="text"] {
- border: 1px solid;
-}
-input[type="checkbox"] {
- width: 1em;
- height: 1em;
-}
-
-.presets {
- -moz-user-select: none;
-}
-.sites {
- border: 0;
- background: white;
- border-collapse: collapse;
- border-spacing: 0;
- width: 100%;
- overflow-y: auto;
-
-}
-.sites tr, .sites td {
- margin: 0;
- padding: 0;
- border: none;
- font-size: 1em;
-}
-.sites > tr.site:hover, .sites > tr.sites:active {
- background: #abf;
-}
-.sites > tr:nth-child(even) {background: #fff}
-.sites > tr:nth-child(odd) {background: #eee}
-
-.site .url {
- padding: 0 0 0 0.5em;
- color: #ccc;
- vertical-align: middle;
-}
-.site .url .protocol { display: none }
-
-.site .url .domain { cursor: help }
-
-[data-key="domain"] .full-address .host,
-[data-key="domain"] .full-address .sub,
-[data-key="domain"] .full-address .protocol,
-[data-key="host"] .full-address span .protocol,
-[data-key="host"] .full-address span .protocol, {
- background-color: #afe;
-}
-[data-key="host"] .full-address span .protocol,
-[data-key="domain"] .full-address span .host,
-[data-key="domain"] .full-address span .protocol {
- border: none;
-}
-
-
-.site .url[data-key="domain"] .domain,
-.site .url[data-key="host"] .domain,
-.site .url[data-key="host"] .sub,
-.site .url[data-key="unsafe"] span {
- color: #a00;
-}
-
-.site .url[data-key="secure"] .domain,
-.site .url[data-key="secure"] .sub,
-.site .url[data-key="full"] span {
- color: black;
-}
-
-.site .url[data-key="full"] span,
-.site .url[data-key="unsafe"] span {
- display: initial;
-}
-
-.site .url .domain {
- font-weight: bold;
-}
-
-input.https-only {
- font-size: 1em;
- -moz-appearance: none;
- background: url(/img/ui-http64.png) no-repeat center;
- background-size: 1.5em;
- width: 1.5em;
- height: 1.5em;
- margin: 0 0 -0.13em 0.13em;
- padding:0;
- cursor: pointer;
-}
-input.https-only:checked {
- background-image: url(/img/ui-https64.png);
-}
-label.https-only {
- display: none;
-}
-
-[data-preset="UNTRUSTED"] .https-only, [data-preset="DEFAULT"] .https-only {
- visibility: hidden;
-}
-
-
-td.presets {
- font-size: 1em;
- white-space: nowrap;
-}
-
-.mobile td.presets {
- white-space: normal;
-}
-
-span.preset {
- position: relative;
- display: inline-block;
- top: 0.13em;
- font-size: 1em;
-}
-
-.preset label, .preset input, .preset button {
- cursor: pointer;
-}
-
-.presets input.preset {
- font-size: 1em;
- -moz-appearance: none;
- background: url(/img/ui-no64.png) no-repeat center left;
- background-size: 1.5em;
- width: 1.5em;
- height: 1.5em;
- outline: 0;
- opacity: .5;
- margin: 0 .5em 0.13em .5em;
-}
-
-input.preset:active, input.preset:focus, input.preset:hover {
- background-color: #ff8;
- border-radius: .5em;
-}
-
-#presets-sizer {
- visibility: hidden;
- position: absolute;
- display: block;
- width: 5000px;
- height: 500px;
- top: -5000px;
-}
-
-#presets-sizer span.preset {
- display: block;
-
-}
-
-.presets input.preset:checked, #presets input.preset, #presets-sizer input.preset {
- opacity: 1;
- transform: none;
- min-width: 9.38em;
- background-color: #ddd;
- border-radius: 0.5em;
-}
-
-.presets input.preset:focus {
- transform: none;
-}
-.sites input + label {
- font-size: 1em;
- line-height: 1.5em;
- vertical-align: top;
-}
-.presets label.preset {
- padding: 0;
- letter-spacing: -0.06em;
- width: 0em;
- overflow: hidden;
- display: none;
- text-transform: uppercase;
- color: #000;
- opacity: .6;
- position: absolute;
- left: 0em;
- padding-left: 2.5em;
-
-}
-
-.presets input.preset[value^="T"] + label {
- text-transform: none;
-}
-
-.presets input.preset:checked + label, #presets .presets label {
- opacity: 1;
- width: 80%;
- display: inline-flex;
-}
-
-#presets-sizer .presets label {
- position: static;
- display: block;
- width: auto;
- overflow: visible;
-}
-
-button.options {
- -moz-appearance: none;
- border: none;
- background: none transparent;
- font-family: sans-serif;
- font-weight: bold;
- color: #048;
- text-shadow: -0.06em -0.06em 0.06em #fff, 0.13em 0.13em 0.13em #000;
- padding: 0;
- margin: 0;
-}
-
-.preset .options {
- -moz-appearance: none;
-
- border: 0;
- background: none;
- font-size: 1em;
- width: 1em;
- height: 1em;
-
- opacity: 0;
- position: absolute;
- bottom: 0.88em;
- left: 1.13em;
-
- pointer-events: none;
-
-}
-
-.preset:hover input.preset:checked ~ .options {
- display: block;
- opacity: 1;
- bottom: 0.38em;
-
-}
-input.preset[value="T_TRUSTED"] {
- background-image: url(/img/ui-temp64.png);
-}
-
-input.preset[value="TRUSTED"] {
- background-image: url(/img/ui-yes64.png)
-}
-input.preset[value="UNTRUSTED"] {
- background-image: url(/img/ui-black64.png)
-}
-input.preset[value="CUSTOM"] {
- background-image: url(/img/ui-custom64.png)
-}
-
-input.temp {
- font-size: 1em;
- -moz-appearance: none;
- margin: 0;
- padding: 0;
- border: 0;
- opacity: 0;
- background: url(/img/ui-clock64.png) no-repeat center;
- background-size: 60%;
- width: 1.5em;
- height: 1.5em;
- transition: 0.2s all;
- right: 0;
- top: 0;
- pointer-events: none;
- position: absolute;
-}
-
-input.temp + label {
- display: none;
-}
-
-input.preset:checked ~ input.temp {
- opacity: .5;
- right: .5em;
- pointer-events: all;
-}
-.presets input.preset:checked ~ input.temp:checked {
- opacity: 1 !important;
- background-size: 100%;
-
-}
-
-.customizing input.preset:checked, #presets input.preset:checked, .customizer fieldset {
- background-color: #ffb !important;
- border-radius: 0.5em 0.5em 0 0;
- margin: 0 0.06em 0.06em 0.06em;
-}
-.customizing input.preset:checked, #presets input.preset, #presets input.preset:checked {
- margin: 0 1em -0.2em 1em;
- border-radius: 0.5em 0.5em 0 0;
-}
-
-.customizing input.preset:checked + label.preset {
- padding-left: 3em;
-}
-
-.customizing, .customizer {
- background-color: #cca !important;
-}
-
-.customizer div {
- transition: 0.2s height;
- padding: 0;
- margin: 0;
-}
-
-span.cap {
- white-space: nowrap;
- display: inline-flex;
-}
-
-.customizer.closed .customizer-controls {
- height: 0;
- overflow: hidden;
-}
-
-span.cap {
- padding: 0.5em;
- font-weight: normal;
-}
-
-span.cap.needed {
- font-weight: bold;
- background-color: #c88;
-}
-
-fieldset {
- border: 0;
- padding: 1.5em 0.5em 0.5em 0.5em;
- margin: 0;
- position: relative;
-}
-
-legend {
- font-weight: bold;
- display: inline;
- position: absolute;
- top: 0.25em;
- left: 1em;
- white-space: nowrap;
-}
-.customizer legend {
- font-weight: bold;
- font-size: 0.75em;
-}
-
-#presets .https-only {
- display: none;
-}
-
-#high-contrast-chooser {
- display: none;
-}
+
+body {
+ font-family: sans-serif;
+ font: -moz-use-system-font;
+ font-size: 12px;
+}
+
+.mobile > body {
+ font-size: 4mm;
+ min-width: auto;
+}
+
+.mobile .desktop {
+ display: none !important;
+}
+
+ @media (max-width: 100mm) {
+ body {
+ background-size: 4em !important;
+ padding-right: 0 !important;
+ }
+
+ .presets {
+ width: 0;
+ }
+
+ .presets input.preset {
+ min-width: 0 !important;
+ background-color: none !important;
+ margin-bottom: 0;
+ margin-top: 1mm;
+ font-weight: bold;
+ }
+ .presets input.temp {
+ position: static;
+ }
+ .presets label.preset {
+ font-size: 50%;
+ top: -1mm;
+ left: 0;
+ margin: 0;
+ padding: 0;
+ text-align: center;
+ text-shadow: 0 0 4px #ff8;
+ position: absolute;
+ overflow: visible;
+ }
+
+ td.presets {
+ white-space: nowrap !important;
+ vertical-align: bottom;
+ }
+ .url {
+ white-space: wrap;
+ word-break: break-all;
+ font-size: 75%;
+ letter-spacing: -0.2mm;
+
+ }
+
+ }
+
+input[type="text"] {
+ border: 1px solid;
+}
+input[type="checkbox"] {
+ width: 1em;
+ height: 1em;
+}
+
+.presets {
+ -moz-user-select: none;
+}
+.sites {
+ border: 0;
+ background: white;
+ border-collapse: collapse;
+ border-spacing: 0;
+ width: 100%;
+ overflow-y: auto;
+
+}
+.sites tr, .sites td {
+ margin: 0;
+ padding: 0;
+ border: none;
+ font-size: 1em;
+}
+.sites > tr.site:hover, .sites > tr.sites:active {
+ background: #abf;
+}
+.sites > tr:nth-child(even) {background: #fff}
+.sites > tr:nth-child(odd) {background: #eee}
+
+.site .url {
+ padding: 0 0 0 0.5em;
+ color: #ccc;
+ vertical-align: middle;
+}
+.site .url .protocol { display: none }
+
+.site .url .domain { cursor: help }
+
+[data-key="domain"] .full-address .host,
+[data-key="domain"] .full-address .sub,
+[data-key="domain"] .full-address .protocol,
+[data-key="host"] .full-address span .protocol,
+[data-key="host"] .full-address span .protocol, {
+ background-color: #afe;
+}
+[data-key="host"] .full-address span .protocol,
+[data-key="domain"] .full-address span .host,
+[data-key="domain"] .full-address span .protocol {
+ border: none;
+}
+
+
+.site .url[data-key="domain"] .domain,
+.site .url[data-key="host"] .domain,
+.site .url[data-key="host"] .sub,
+.site .url[data-key="unsafe"] span {
+ color: #a00;
+}
+
+.site .url[data-key="secure"] .domain,
+.site .url[data-key="secure"] .sub,
+.site .url[data-key="full"] span {
+ color: black;
+}
+
+.site .url[data-key="full"] span,
+.site .url[data-key="unsafe"] span {
+ display: initial;
+}
+
+.site .url .domain {
+ font-weight: bold;
+}
+
+input.https-only {
+ font-size: 1em;
+ -moz-appearance: none;
+ background: url(/img/ui-http64.png) no-repeat center;
+ background-size: 1.5em;
+ width: 1.5em;
+ height: 1.5em;
+ margin: 0 0 -0.13em 0.13em;
+ padding:0;
+ cursor: pointer;
+}
+input.https-only:checked {
+ background-image: url(/img/ui-https64.png);
+}
+label.https-only {
+ display: none;
+}
+
+[data-preset="UNTRUSTED"] .https-only, [data-preset="DEFAULT"] .https-only {
+ visibility: hidden;
+}
+
+
+td.presets {
+ font-size: 1em;
+ white-space: nowrap;
+}
+
+.mobile td.presets {
+ white-space: normal;
+}
+
+span.preset {
+ position: relative;
+ display: inline-block;
+ top: 0.13em;
+ font-size: 1em;
+}
+
+.preset label, .preset input, .preset button {
+ cursor: pointer;
+}
+
+.presets input.preset {
+ font-size: 1em;
+ -moz-appearance: none;
+ background: url(/img/ui-no64.png) no-repeat center left;
+ background-size: 1.5em;
+ width: 1.5em;
+ height: 1.5em;
+ outline: 0;
+ opacity: .5;
+ margin: 0 .5em 0.13em .5em;
+}
+
+input.preset:active, input.preset:focus, input.preset:hover {
+ background-color: #ff8;
+ border-radius: .5em;
+}
+
+#presets-sizer {
+ visibility: hidden;
+ position: absolute;
+ display: block;
+ width: 5000px;
+ height: 500px;
+ top: -5000px;
+}
+
+#presets-sizer span.preset {
+ display: block;
+
+}
+
+.presets input.preset:checked, #presets input.preset, #presets-sizer input.preset {
+ opacity: 1;
+ transform: none;
+ min-width: 9.38em;
+ background-color: #ddd;
+ border-radius: 0.5em;
+}
+
+.presets input.preset:focus {
+ transform: none;
+}
+.sites input + label {
+ font-size: 1em;
+ line-height: 1.5em;
+ vertical-align: top;
+}
+.presets label.preset {
+ padding: 0;
+ letter-spacing: -0.06em;
+ width: 0em;
+ overflow: hidden;
+ display: none;
+ text-transform: uppercase;
+ color: #000;
+ opacity: .6;
+ position: absolute;
+ left: 0em;
+ padding-left: 2.5em;
+
+}
+
+.presets input.preset[value^="T"] + label {
+ text-transform: none;
+}
+
+.presets input.preset:checked + label, #presets .presets label {
+ opacity: 1;
+ width: 80%;
+ display: inline-flex;
+}
+
+#presets-sizer .presets label {
+ position: static;
+ display: block;
+ width: auto;
+ overflow: visible;
+}
+
+button.options {
+ -moz-appearance: none;
+ border: none;
+ background: none transparent;
+ font-family: sans-serif;
+ font-weight: bold;
+ color: #048;
+ text-shadow: -0.06em -0.06em 0.06em #fff, 0.13em 0.13em 0.13em #000;
+ padding: 0;
+ margin: 0;
+}
+
+.preset .options {
+ -moz-appearance: none;
+
+ border: 0;
+ background: none;
+ font-size: 1em;
+ width: 1em;
+ height: 1em;
+
+ opacity: 0;
+ position: absolute;
+ bottom: 0.88em;
+ left: 1.13em;
+
+ pointer-events: none;
+
+}
+
+.preset:hover input.preset:checked ~ .options {
+ display: block;
+ opacity: 1;
+ bottom: 0.38em;
+
+}
+input.preset[value="T_TRUSTED"] {
+ background-image: url(/img/ui-temp64.png);
+}
+
+input.preset[value="TRUSTED"] {
+ background-image: url(/img/ui-yes64.png)
+}
+input.preset[value="UNTRUSTED"] {
+ background-image: url(/img/ui-black64.png)
+}
+input.preset[value="CUSTOM"] {
+ background-image: url(/img/ui-custom64.png)
+}
+
+input.temp {
+ font-size: 1em;
+ -moz-appearance: none;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ opacity: 0;
+ background: url(/img/ui-clock64.png) no-repeat center;
+ background-size: 60%;
+ width: 1.5em;
+ height: 1.5em;
+ transition: 0.2s all;
+ right: 0;
+ top: 0;
+ pointer-events: none;
+ position: absolute;
+}
+
+input.temp + label {
+ display: none;
+}
+
+input.preset:checked ~ input.temp {
+ opacity: .5;
+ right: .5em;
+ pointer-events: all;
+}
+.presets input.preset:checked ~ input.temp:checked {
+ opacity: 1 !important;
+ background-size: 100%;
+
+}
+
+.customizing input.preset:checked, #presets input.preset:checked, .customizer fieldset {
+ background-color: #ffb !important;
+ border-radius: 0.5em 0.5em 0 0;
+ margin: 0 0.06em 0.06em 0.06em;
+}
+.customizing input.preset:checked, #presets input.preset, #presets input.preset:checked {
+ margin: 0 1em -0.2em 1em;
+ border-radius: 0.5em 0.5em 0.5em 0.5em;
+}
+
+.customizing input.preset:checked + label.preset {
+ padding-left: 3em;
+}
+
+.customizing, .customizer {
+ background-color: rgb(243, 243, 243) !important;
+}
+
+.customizer div {
+ transition: 0.2s height;
+ padding: 0;
+ margin: 0;
+}
+
+span.cap {
+ white-space: nowrap;
+ display: inline-flex;
+}
+
+.customizer.closed .customizer-controls {
+ height: 0;
+ overflow: hidden;
+}
+
+span.cap {
+ padding: 0.5em;
+ font-weight: normal;
+}
+
+span.cap.needed {
+ font-weight: bold;
+ background-color: #c88;
+}
+
+fieldset {
+ border: 0;
+ padding: 1.5em 0.5em 0.5em 0.5em;
+ margin: 0;
+ position: relative;
+}
+
+legend {
+ font-weight: bold;
+ display: inline;
+ position: absolute;
+ top: 0.25em;
+ left: 1em;
+ white-space: nowrap;
+}
+.customizer legend {
+ font-weight: bold;
+ font-size: 0.75em;
+}
+
+#presets .https-only {
+ display: none;
+}
+
+#high-contrast-chooser {
+ display: none;
+}
diff --git a/src/ui/ui.js b/src/ui/ui.js
index 4a9c7c82..f416fba5 100644
--- a/src/ui/ui.js
+++ b/src/ui/ui.js
@@ -1,759 +1,759 @@
-'use strict';
-var UI = (() => {
-
- var UI = {
- initialized: false,
-
- presets: {
- "DEFAULT": "Default",
- "T_TRUSTED": "Trusted_temporary",
- "TRUSTED": "Trusted_permanent",
- "UNTRUSTED": "Untrusted",
- "CUSTOM": "Custom",
- },
-
- async init(tabId = -1) {
- UI.tabId = tabId;
- let scripts = [
- "/ui/ui.css",
- "/lib/Messages.js",
- "/lib/punycode.js",
- "/lib/tld.js",
- "/common/Policy.js",
- ];
- this.mobile = !("windows" in browser);
- if (this.mobile) {
- document.documentElement.classList.toggle("mobile", true);
- scripts.push("/lib/fastclick.js");
- }
- await include(scripts);
-
- let inited = new Promise(resolve => {
- Messages.addHandler({
- async settings(m) {
- UI.policy = new Policy(m.policy);
- UI.snapshot = UI.policy.snapshot;
- UI.seen = m.seen;
- UI.unrestrictedTab = m.unrestrictedTab;
- UI.xssUserChoices = m.xssUserChoices;
- UI.local = m.local;
- UI.sync = m.sync;
- if (UI.local && !UI.local.debug) {
- debug = () => {}; // be quiet!
- }
- resolve();
- if (UI.onSettings) UI.onSettings();
- await HighContrast.init();
- }
- });
-
- if (this.mobile) FastClick.attach(document.body);
- UI.pullSettings();
- });
-
- await inited;
-
- this.initialized = true;
- debug("Imported", Policy);
- },
- async pullSettings() {
- Messages.send("broadcastSettings", {tabId: UI.tabId});
- },
- async updateSettings({policy, xssUserChoices, unrestrictedTab, local, sync, reloadAffected}) {
- if (policy) policy = policy.dry(true);
- return await Messages.send("updateSettings", {
- policy,
- xssUserChoices,
- unrestrictedTab,
- local,
- sync,
- reloadAffected,
- tabId: UI.tabId,
- });
- },
-
- async exportSettings() {
- return await Messages.send("exportSettings");
- },
- async importSettings(data) {
- return await Messages.send("importSettings", {data});
- },
-
- async revokeTemp() {
- let policy = this.policy;
- Policy.hydrate(policy.dry(), policy);
- if (this.isDirty(true)) {
- await this.updateSettings({policy, reloadAffected: true});
- }
- },
-
- isDirty(reset = false) {
- let currentSnapshot = this.policy.snapshot;
- let dirty = currentSnapshot != this.snapshot;
- if (reset) this.snapshot = currentSnapshot;
- return dirty;
- },
-
- async openSiteInfo(domain) {
- let url = `/ui/siteInfo.html#${encodeURIComponent(domain)};${UI.tabId}`;
- browser.tabs.create({url});
- },
-
- wireOption(name, storage = "sync", onchange) {
- let input = document.querySelector(`#opt-${name}`);
- if (!input) {
- debug("Checkbox not found %s", name);
- return;
- }
- if (typeof storage === "function") {
- input.onchange = e => storage(input);
- input.checked = storage(null);
- } else {
- let obj = UI[storage];
- if (!obj) log(storage);
- input.checked = obj[name];
- if (onchange) onchange(input.checked);
- input.onchange = async () => {
- obj[name] = input.checked;
- await UI.updateSettings({[storage]: obj});
- if (onchange) onchange(obj[name]);
- }
- }
- return input;
- }
- };
-
- var HighContrast = {
- css: null,
- async init() {
- this.widget = UI.wireOption("highContrast", "local", value => {
- UI.highContrast = value;
- this.toggle();
- });
- await this.toggle();
- },
- async toggle() {
- let hc = "highContrast" in UI ? UI.highContrast : await this.detect();
- if (hc) {
- if (this.css) {
- document.documentElement.appendChild(this.css);
- } else {
- this.css = await include("/ui/ui-hc.css")
- }
- } else if (this.css) {
- this.css.remove();
- }
- document.documentElement.classList.toggle("hc", hc);
- if (this.widget) {
- this.widget.checked = hc;
- }
- },
-
- detect() {
- if ("highContrast" in UI.local) {
- UI.highContrast = UI.local.highContrast;
- } else {
- // auto-detect
- let canary = document.createElement("input");
- canary.className="https-only";
- canary.style.display = "none";
- document.body.appendChild(canary);
- UI.highContrast = window.getComputedStyle(canary).backgroundImage === "none";
- canary.parentNode.removeChild(canary);
- }
- return UI.highContrast;
- }
- };
-
- function fireOnChange(sitesUI, data) {
- if (UI.isDirty(true)) {
- UI.updateSettings({policy: UI.policy});
- if (sitesUI.onChange) sitesUI.onChange(data, this);
- }
- }
-
- function compareBy(prop, a, b) {
- let x = a[prop], y = b[prop];
- return x > y ? 1 : x < y ? -1 : 0;
- }
-
- const TEMPLATE = `
-
- `;
-
- const TEMP_PRESETS = ["CUSTOM"];
- const DEF_PRESETS = {
- // name: customizable,
- "DEFAULT": false,
- "T_TRUSTED": false,
- "TRUSTED": false,
- "UNTRUSTED": false,
- "CUSTOM": true,
- };
-
- UI.Sites = class {
- constructor(parentNode, presets = DEF_PRESETS) {
- this.parentNode = parentNode;
- let policy = UI.policy;
- this.uiCount = UI.Sites.count = (UI.Sites.count || 0) + 1;
- this.sites = policy.sites;
- this.presets = presets;
- this.customizing = null;
- this.typesMap = new Map();
- this.clear();
- }
-
- initRow(table = this.table) {
- let row = table.querySelector("tr.site");
- // PRESETS
- {
- let presets = row.querySelector(".presets");
- let [span, input, label, options] = presets.querySelectorAll("span.preset, input.preset, label.preset, .options");
- span.remove();
- options.title = _("Options");
- for (let [preset, customizable] of Object.entries(this.presets)) {
- let messageKey = UI.presets[preset];
- input.value = preset;
- label.textContent = label.title = input.title = _(messageKey);
- let clone = span.cloneNode(true);
- clone.classList.add(preset);
- let temp = clone.querySelector(".temp");
- if (TEMP_PRESETS.includes(preset)) {
- temp.title = _("allowTemp", `(${label.title.toUpperCase()})`);
- temp.nextElementSibling.textContent = _("allowTemp", ""); // label;
- } else {
- temp.nextElementSibling.remove();
- temp.remove();
- }
- if (customizable) {
- clone.querySelector(".options").remove();
- }
- presets.appendChild(clone);
-
- }
-
- if (!UI.mobile) {
- UI.Sites.correctSize(presets);
- }
-
- }
-
- // URL
- {
- let [input, label] = row.querySelectorAll("input.https-only, label.https-only");
- input.title = label.title = label.textContent = _("httpsOnly");
- }
-
- // CUSTOMIZER ROW
- {
- let [customizer, legend, cap, capInput, capLabel] = table.querySelectorAll(".customizer, legend, span.cap, input.cap, label.cap");
- row._customizer = customizer;
- customizer.remove();
- let capParent = cap.parentNode;
- capParent.removeChild(cap);
- legend.textContent = _("allow");
- let idSuffix = UI.Sites.count;
- for (let capability of Permissions.ALL) {
- capInput.id = `capability-${capability}-${idSuffix}`
- capLabel.setAttribute("for", capInput.id);
- capInput.value = capability;
- capInput.title = capLabel.textContent = _(`cap_${capability}`);
- let clone = capParent.appendChild(cap.cloneNode(true));
- clone.classList.add(capability);
- }
- }
-
- // debug(table.outerHTML);
- return row;
- }
-
- static correctSize(presets) {
- // adapt button to label if needed
- let sizer = document.createElement("div");
- sizer.id = "presets-sizer";
- sizer.appendChild(presets.cloneNode(true));
- document.body.appendChild(sizer);
- setTimeout(async () => {
- let presetWidth = sizer.querySelector("input.preset").offsetWidth;
- let labelWidth = 0;
- for (let l of sizer.querySelectorAll("label.preset")) {
- let lw = l.offsetWidth;
- debug("lw", l.textContent, lw);
- if (lw > labelWidth) labelWidth = lw;
- }
-
- debug(`Preset: %s Label: %s`, presetWidth, labelWidth);
- labelWidth += 16;
- if (presetWidth < labelWidth) {
- for (let ss of document.styleSheets) {
- if (ss.href.endsWith("/ui.css")) {
- for (let r of ss.cssRules) {
- if (/input\.preset:checked.*min-width:/.test(r.cssText)) {
- r.style.minWidth = (labelWidth) + "px";
- break;
- }
- }
- }
- }
- }
-
- sizer.remove();
-
- }, 100);
- UI.Sites.correctSize = () => {}; // just once, please!
- }
-
- allSiteRows() {
- return this.table.querySelectorAll("tr.site");
- }
- clear() {
- debug("Clearing list", this.table);
-
- this.template = document.createElement("template");
- this.template.innerHTML = TEMPLATE;
- this.fragment = this.template.content;
- this.table = this.fragment.querySelector("table.sites");
- this.rowTemplate = this.initRow();
-
- for (let r of this.allSiteRows()) {
- r.parentNode.removeChild(r);
- }
- this.customize(null);
- this.sitesCount = 0;
- }
-
- siteNeeds(site, type) {
- let siteTypes = this.typesMap && this.typesMap.get(site);
- return !!siteTypes && siteTypes.has(type);
- }
-
- handleEvent(ev) {
- let target = ev.target;
- let customizer = target.closest(".customizer");
- let row = customizer ? customizer.parentNode.querySelector("tr.customizing") : target.closest("tr.site");
- if (!row) return;
- row.temp2perm = false;
- let isTemp = target.matches("input.temp");
- let preset = target.matches("input.preset") ? target
- : customizer || isTemp ? row.querySelector("input.preset:checked")
- : target.closest("input.preset");
- debug("%s target %o\n\trow %s, perms %o\npreset %s %s",
- ev.type,
- target, row && row.siteMatch, row && row.perms,
- preset && preset.value, preset && preset.checked);
-
- if (!preset) {
- if (target.matches("input.https-only") && ev.type === "change") {
- this.toggleSecure(row, target.checked);
- fireOnChange(this, row);
- } else if (target.matches(".domain")) {
- UI.openSiteInfo(row.domain);
- }
- return;
- }
-
- let policy = UI.policy;
- let {siteMatch, contextMatch, perms} = row;
- let presetValue = preset.value;
- let policyPreset = presetValue.startsWith("T_") ? policy[presetValue.substring(2)].tempTwin : policy[presetValue];
-
- if (policyPreset) {
- if (row.perms !== policyPreset) {
- row.temp2perm = row.perms && policyPreset.tempTwin === row.perms;
- row.perms = policyPreset;
- }
- }
-
-
- let isCap = customizer && target.matches(".cap");
- let tempToggle = preset.parentNode.querySelector("input.temp");
-
- if (ev.type === "change") {
- if (preset.checked) {
- row.dataset.preset = preset.value;
- }
- if (isCap) {
- perms.set(target.value, target.checked);
- } else if (policyPreset) {
- if (tempToggle && tempToggle.checked) {
- policyPreset = policyPreset.tempTwin;
- }
- row.contextMatch = null;
- row.perms = policyPreset;
- delete row._customPerms;
- debug("Site match", siteMatch);
- if (siteMatch) {
- policy.set(siteMatch, policyPreset);
- } else {
- this.customize(policyPreset, preset, row);
- }
-
- } else if (preset.value === "CUSTOM") {
- if (isTemp) {
- row.perms.temp = target.checked;
- } else {
- let temp = row.perms.temp;
- tempToggle.checked = temp;
- let perms = row._customPerms ||
- (row._customPerms = new Permissions(new Set(row.perms.capabilities), temp));
- row.perms = perms;
- policy.set(siteMatch, perms);
- this.customize(perms, preset, row);
- }
- }
- fireOnChange(this, row);
- } else if (!(isCap || isTemp) && ev.type === "click") {
- this.customize(row.perms, preset, row);
- }
- }
-
- customize(perms, preset, row) {
- debug("Customize preset %s (%o) - Dirty: %s", preset && preset.value, perms, this.dirty);
- for(let r of this.table.querySelectorAll("tr.customizing")) {
- r.classList.toggle("customizing", false);
- }
- let customizer = this.rowTemplate._customizer;
- customizer.classList.toggle("closed", true);
-
- if (!(perms && row && preset &&
- row.dataset.preset === preset.value &&
- this.presets[preset.value] &&
- preset !== customizer._preset)) {
- delete customizer._preset;
- return;
- }
-
- customizer._preset = preset;
- row.classList.toggle("customizing", true);
- let immutable = Permissions.IMMUTABLE[preset.value] || {};
- for (let input of customizer.querySelectorAll("input")) {
- let type = input.value;
- if (type in immutable) {
- input.disabled = true;
- input.checked = immutable[type];
- } else {
- input.checked = perms.allowing(type);
- input.disabled = false;
- }
- input.parentNode.classList.toggle("needed", this.siteNeeds(row._site, type));
- row.parentNode.insertBefore(customizer, row.nextElementSibling);
- customizer.classList.toggle("closed", false);
- customizer.onkeydown = e => {
- switch(e.keyCode) {
- case 38:
- case 8:
- e.preventDefault();
- this.onkeydown = null;
- this.customize(null);
- preset.focus();
- return false;
- }
- }
- window.setTimeout(() => customizer.querySelector("input").focus(), 50);
- }
- }
-
- render(sites = this.sites, sorter = this.sorter) {
- let parentNode = this.parentNode;
- debug("Rendering %o inside %o", sites, parentNode);
- if (sites) this._populate(sites, sorter);
- parentNode.innerHTML = "";
- parentNode.appendChild(this.fragment);
- let root = parentNode.querySelector("table.sites");
- debug("Wiring", root);
- if (!root.wiredBy) {
- root.addEventListener("click", this, true);
- root.addEventListener("change", this, true);
- root.wiredBy = this;
- }
- return root;
- }
-
- _populate(sites, sorter) {
- this.clear();
- if (sites instanceof Sites) {
- for (let [site, perms] of sites) {
- this.append(site, site, perms);
- }
- } else {
- for (let site of sites) {
- let context = null;
- if (site.site) {
- site = site.site;
- context = site.context;
- }
- let {siteMatch, perms, contextMatch} = UI.policy.get(site, context);
- this.append(site, siteMatch, perms, contextMatch);
- }
- this.sites = sites;
- }
- this.sort(sorter);
- window.setTimeout(() => this.focus(), 50);
- }
-
- focus() {
- let firstPreset = this.table.querySelector("input.preset:checked");
- if (firstPreset) firstPreset.focus();
- }
-
- sort(sorter = this.sorter) {
- if (this.mainDomain) {
- let md = this.mainDomain;
- let wrappedCompare = sorter;
- sorter = (a, b) => {
- let x = a.domain, y = b.domain;
- if (x === md) {
- if (y !== md) {
- return -1;
- }
- } else if (y === md) {
- return 1;
- }
- return wrappedCompare(a, b);
- }
- }
- let rows = [...this.allSiteRows()].sort(sorter);
- if (this.mainSite) {
- let mainLabel = "." + this.mainDomain;
- let topIdx = rows.findIndex(r => r._label === mainLabel);
- if (topIdx === -1) rows.findIndex(r => r._site === this.mainSite);
- if (topIdx !== -1) {
- // move the row to the top
- let topRow = rows.splice(topIdx, 1)[0];
- rows.unshift(topRow);
- topRow.classList.toggle("main", true);
- }
- }
- this.clear();
- for (let row of rows) this.table.appendChild(row);
- this.table.appendChild(this.rowTemplate._customizer);
- }
-
- sorter(a, b) {
- return compareBy("domain", a, b) || compareBy("_label", a, b);
- }
-
- async tempTrustAll() {
- let {policy} = UI;
- let changed = 0;
- for (let row of this.allSiteRows()) {
- if (row._preset === "DEFAULT") {
- policy.set(row._site, policy.TRUSTED.tempTwin);
- changed++;
- }
- }
- if (changed && UI.isDirty(true)) {
- await UI.updateSettings({policy, reloadAffected: true});
- }
- return changed;
- }
-
- createSiteRow(site, siteMatch, perms, contextMatch = null, sitesCount = this.sitesCount++) {
- debug("Creating row for site: %s, matching %s / %s, %o", site, siteMatch, contextMatch, perms);
-
- let row = this.rowTemplate.cloneNode(true);
- row.sitesCount = sitesCount;
- let url;
- try {
- url = new URL(site);
- } catch (e) {
- if (/^(\w+:)\/*$/.test(site)) {
- url = {protocol: RegExp.$1, hostname: "", origin: site, pathname:""};
- let hostname = Sites.toExternal(url.hostname);
- debug("Lonely %o", url, Sites.isSecureDomainKey(siteMatch) || !hostname && url.protocol === "https:");
- } else {
- let protocol = Sites.isSecureDomainKey(site) ? "https:" : "http:";
- let hostname = Sites.toggleSecureDomainKey(site, false);
- url = {protocol, hostname, origin: `${protocol}://${site}`, pathname: "/"};
- }
- }
-
- let hostname = Sites.toExternal(url.hostname);
- let domain = tld.getDomain(hostname);
-
- if (!siteMatch) {
- siteMatch = site;
- }
- let secure = Sites.isSecureDomainKey(siteMatch);
- let keyStyle = secure ? "secure"
- : !domain || /^\w+:/.test(siteMatch) ?
- (url.protocol === "https:" ? "full" : "unsafe")
- : domain === hostname ? "domain" : "host";
-
- let urlContainer = row.querySelector(".url");
- urlContainer.dataset.key = keyStyle;
- row._site = site;
-
- row.siteMatch = siteMatch;
- row.contextMatch = contextMatch;
- row.perms = perms;
- row.domain = domain || siteMatch;
- if (domain) { // "normal" URL
- let justDomain = hostname === domain;
- let domainEntry = secure || domain === site;
- row._label = domainEntry ? "." + domain : site;
- row.querySelector(".protocol").textContent = `${url.protocol}//`;
- row.querySelector(".sub").textContent = justDomain ?
- (keyStyle === "full" || keyStyle == "unsafe"
- ? "" : "…")
- : hostname.substring(0, hostname.length - domain.length);
-
- row.querySelector(".domain").textContent = domain;
- row.querySelector(".path").textContent = siteMatch.length > url.origin.length ? url.pathname : "";
- } else {
- row._label = siteMatch;
- urlContainer.querySelector(".full-address").textContent = siteMatch;
- }
- let httpsOnly = row.querySelector("input.https-only");
- httpsOnly.checked = keyStyle === "full" || keyStyle === "secure";
-
- let presets = row.querySelectorAll("input.preset");
- let idSuffix = `-${this.uiCount}-${sitesCount}`;
- for (let p of presets) {
- p.id = `${p.value}${idSuffix}`;
- p.name = `preset${idSuffix}`;
- let label = p.nextElementSibling;
- label.setAttribute("for", p.id);
- let temp = p.parentNode.querySelector("input.temp");
- if (temp) {
- temp.id = `temp-${p.id}`;
- label = temp.nextElementSibling;
- label.setAttribute("for", temp.id);
- }
- }
- let policy = UI.policy;
-
- let presetName = "CUSTOM";
- for (let p of ["TRUSTED", "UNTRUSTED", "DEFAULT"]) {
- let preset = policy[p];
- switch (perms) {
- case preset:
- presetName = p;
- break;
- case preset.tempTwin:
- presetName = `T_${p}`;
- if (!presetName in UI.presets) {
- presetName = p;
- }
- break;
- }
- }
- let tempFirst = true; // TODO: make it a preference
- let unsafeMatch = keyStyle !== "secure" && keyStyle !== "full";
- if (presetName === "DEFAULT" && (tempFirst || unsafeMatch)) {
- // prioritize temporary privileges over permanent
- for (let p of TEMP_PRESETS) {
- if (p in this.presets && (unsafeMatch || tempFirst && p === "TRUSTED")) {
- row.querySelector(`.presets input[value="${p}"]`).parentNode.querySelector("input.temp").checked = true;
- perms = policy.TRUSTED.tempTwin;
- }
- }
- }
- let preset = row.querySelector(`.presets input[value="${presetName}"]`);
- if (!preset) {
- debug(`Preset %s not found in %s!`, presetName, row.innerHTML);
- } else {
- preset.checked = true;
- row.dataset.preset = row._preset = presetName;
- if (TEMP_PRESETS.includes(presetName)) {
- let temp = preset.parentNode.querySelector("input.temp");
- if (temp) {
- temp.checked = perms.temp;
- }
- }
- }
- return row;
- }
-
- append(site, siteMatch, perms, contextMatch) {
- this.table.appendChild(this.createSiteRow(...arguments));
- }
-
- toggleSecure(row, secure = !!row.querySelector("https-only:checked")) {
- this.customize(null);
- let site = row.siteMatch;
- site = site.replace(/^https?:/, secure ? "https:" : "http:");
- if (site === row.siteMatch) {
- site = Sites.toggleSecureDomainKey(site, secure);
- }
- if (site !== row.siteMatch) {
- let {policy} = UI;
- policy.set(row.siteMatch, policy.DEFAULT);
- policy.set(site, row.perms);
- for(let r of this.allSiteRows()) {
- if (r !== row && r.siteMatch === site && r.contextMatch === row.contextMatch) {
- r.parentNode.removeChild(r);
- }
- }
- let newRow = this.createSiteRow(site, site, row.perms, row.contextMatch, row.sitesCount);
- row.parentNode.replaceChild(newRow, row);
- }
- }
-
- highlight(key) {
- key = Sites.toExternal(key);
- for (let r of this.allSiteRows()) {
- if (r.querySelector(".full-address").textContent.trim().includes(key)) {
- let url = r.lastElementChild;
- url.style.transition = r.style.transition = "none";
- r.style.backgroundColor = "#850";
- url.style.transform = "scale(2)";
- r.querySelector("input.preset:checked").focus();
- window.setTimeout(() => {
- r.style.transition = "1s background-color";
- url.style.transition = "1s transform";
- r.style.backgroundColor = "";
- url.style.transform = "none";
- r.scrollIntoView();
- }, 50);
- }
- }
- }
-
- filterSites(key) {
- key = Sites.toExternal(key);
- for (let r of this.allSiteRows()) {
- if (r.querySelector(".full-address").textContent.trim().includes(key)) {
- r.style.display = "";
- } else {
- r.style.display = "none";
- }
- }
- }
- }
-
- return UI;
-})();
+'use strict';
+var UI = (() => {
+
+ var UI = {
+ initialized: false,
+
+ presets: {
+ "DEFAULT": "Default",
+ "T_TRUSTED": "Trusted_temporary",
+ "TRUSTED": "Trusted_permanent",
+ "UNTRUSTED": "Untrusted",
+ "CUSTOM": "Custom",
+ },
+
+ async init(tabId = -1) {
+ UI.tabId = tabId;
+ let scripts = [
+ "/ui/ui.css",
+ "/lib/Messages.js",
+ "/lib/punycode.js",
+ "/lib/tld.js",
+ "/common/Policy.js",
+ ];
+ this.mobile = !("windows" in browser);
+ if (this.mobile) {
+ document.documentElement.classList.toggle("mobile", true);
+ scripts.push("/lib/fastclick.js");
+ }
+ await include(scripts);
+
+ let inited = new Promise(resolve => {
+ Messages.addHandler({
+ async settings(m) {
+ UI.policy = new Policy(m.policy);
+ UI.snapshot = UI.policy.snapshot;
+ UI.seen = m.seen;
+ UI.unrestrictedTab = m.unrestrictedTab;
+ UI.xssUserChoices = m.xssUserChoices;
+ UI.local = m.local;
+ UI.sync = m.sync;
+ if (UI.local && !UI.local.debug) {
+ debug = () => {}; // be quiet!
+ }
+ resolve();
+ if (UI.onSettings) UI.onSettings();
+ await HighContrast.init();
+ }
+ });
+
+ if (this.mobile) FastClick.attach(document.body);
+ UI.pullSettings();
+ });
+
+ await inited;
+
+ this.initialized = true;
+ debug("Imported", Policy);
+ },
+ async pullSettings() {
+ Messages.send("broadcastSettings", {tabId: UI.tabId});
+ },
+ async updateSettings({policy, xssUserChoices, unrestrictedTab, local, sync, reloadAffected}) {
+ if (policy) policy = policy.dry(true);
+ return await Messages.send("updateSettings", {
+ policy,
+ xssUserChoices,
+ unrestrictedTab,
+ local,
+ sync,
+ reloadAffected,
+ tabId: UI.tabId,
+ });
+ },
+
+ async exportSettings() {
+ return await Messages.send("exportSettings");
+ },
+ async importSettings(data) {
+ return await Messages.send("importSettings", {data});
+ },
+
+ async revokeTemp() {
+ let policy = this.policy;
+ Policy.hydrate(policy.dry(), policy);
+ if (this.isDirty(true)) {
+ await this.updateSettings({policy, reloadAffected: true});
+ }
+ },
+
+ isDirty(reset = false) {
+ let currentSnapshot = this.policy.snapshot;
+ let dirty = currentSnapshot != this.snapshot;
+ if (reset) this.snapshot = currentSnapshot;
+ return dirty;
+ },
+
+ async openSiteInfo(domain) {
+ let url = `/ui/siteInfo.html#${encodeURIComponent(domain)};${UI.tabId}`;
+ browser.tabs.create({url});
+ },
+
+ wireOption(name, storage = "sync", onchange) {
+ let input = document.querySelector(`#opt-${name}`);
+ if (!input) {
+ debug("Checkbox not found %s", name);
+ return;
+ }
+ if (typeof storage === "function") {
+ input.onchange = e => storage(input);
+ input.checked = storage(null);
+ } else {
+ let obj = UI[storage];
+ if (!obj) log(storage);
+ input.checked = obj[name];
+ if (onchange) onchange(input.checked);
+ input.onchange = async () => {
+ obj[name] = input.checked;
+ await UI.updateSettings({[storage]: obj});
+ if (onchange) onchange(obj[name]);
+ }
+ }
+ return input;
+ }
+ };
+
+ var HighContrast = {
+ css: null,
+ async init() {
+ this.widget = UI.wireOption("highContrast", "local", value => {
+ UI.highContrast = value;
+ this.toggle();
+ });
+ await this.toggle();
+ },
+ async toggle() {
+ let hc = "highContrast" in UI ? UI.highContrast : await this.detect();
+ if (hc) {
+ if (this.css) {
+ document.documentElement.appendChild(this.css);
+ } else {
+ this.css = await include("/ui/ui-hc.css")
+ }
+ } else if (this.css) {
+ this.css.remove();
+ }
+ document.documentElement.classList.toggle("hc", hc);
+ if (this.widget) {
+ this.widget.checked = hc;
+ }
+ },
+
+ detect() {
+ if ("highContrast" in UI.local) {
+ UI.highContrast = UI.local.highContrast;
+ } else {
+ // auto-detect
+ let canary = document.createElement("input");
+ canary.className="https-only";
+ canary.style.display = "none";
+ document.body.appendChild(canary);
+ UI.highContrast = window.getComputedStyle(canary).backgroundImage === "none";
+ canary.parentNode.removeChild(canary);
+ }
+ return UI.highContrast;
+ }
+ };
+
+ function fireOnChange(sitesUI, data) {
+ if (UI.isDirty(true)) {
+ UI.updateSettings({policy: UI.policy});
+ if (sitesUI.onChange) sitesUI.onChange(data, this);
+ }
+ }
+
+ function compareBy(prop, a, b) {
+ let x = a[prop], y = b[prop];
+ return x > y ? 1 : x < y ? -1 : 0;
+ }
+
+ const TEMPLATE = `
+
+ `;
+
+ const TEMP_PRESETS = ["CUSTOM"];
+ const DEF_PRESETS = {
+ // name: customizable,
+ "DEFAULT": false,
+ "T_TRUSTED": false,
+ "TRUSTED": false,
+ "UNTRUSTED": false,
+ "CUSTOM": true,
+ };
+
+ UI.Sites = class {
+ constructor(parentNode, presets = DEF_PRESETS) {
+ this.parentNode = parentNode;
+ let policy = UI.policy;
+ this.uiCount = UI.Sites.count = (UI.Sites.count || 0) + 1;
+ this.sites = policy.sites;
+ this.presets = presets;
+ this.customizing = null;
+ this.typesMap = new Map();
+ this.clear();
+ }
+
+ initRow(table = this.table) {
+ let row = table.querySelector("tr.site");
+ // PRESETS
+ {
+ let presets = row.querySelector(".presets");
+ let [span, input, label, options] = presets.querySelectorAll("span.preset, input.preset, label.preset, .options");
+ span.remove();
+ options.title = _("Options");
+ for (let [preset, customizable] of Object.entries(this.presets)) {
+ let messageKey = UI.presets[preset];
+ input.value = preset;
+ label.textContent = label.title = input.title = _(messageKey);
+ let clone = span.cloneNode(true);
+ clone.classList.add(preset);
+ let temp = clone.querySelector(".temp");
+ if (TEMP_PRESETS.includes(preset)) {
+ temp.title = _("allowTemp", `(${label.title.toUpperCase()})`);
+ temp.nextElementSibling.textContent = _("allowTemp", ""); // label;
+ } else {
+ temp.nextElementSibling.remove();
+ temp.remove();
+ }
+ if (customizable) {
+ clone.querySelector(".options").remove();
+ }
+ presets.appendChild(clone);
+
+ }
+
+ if (!UI.mobile) {
+ UI.Sites.correctSize(presets);
+ }
+
+ }
+
+ // URL
+ {
+ let [input, label] = row.querySelectorAll("input.https-only, label.https-only");
+ input.title = label.title = label.textContent = _("httpsOnly");
+ }
+
+ // CUSTOMIZER ROW
+ {
+ let [customizer, legend, cap, capInput, capLabel] = table.querySelectorAll(".customizer, legend, span.cap, input.cap, label.cap");
+ row._customizer = customizer;
+ customizer.remove();
+ let capParent = cap.parentNode;
+ capParent.removeChild(cap);
+ legend.textContent = _("allow");
+ let idSuffix = UI.Sites.count;
+ for (let capability of Permissions.ALL) {
+ capInput.id = `capability-${capability}-${idSuffix}`
+ capLabel.setAttribute("for", capInput.id);
+ capInput.value = capability;
+ capInput.title = capLabel.textContent = _(`cap_${capability}`);
+ let clone = capParent.appendChild(cap.cloneNode(true));
+ clone.classList.add(capability);
+ }
+ }
+
+ // debug(table.outerHTML);
+ return row;
+ }
+
+ static correctSize(presets) {
+ // adapt button to label if needed
+ let sizer = document.createElement("div");
+ sizer.id = "presets-sizer";
+ sizer.appendChild(presets.cloneNode(true));
+ document.body.appendChild(sizer);
+ setTimeout(async () => {
+ let presetWidth = sizer.querySelector("input.preset").offsetWidth;
+ let labelWidth = 0;
+ for (let l of sizer.querySelectorAll("label.preset")) {
+ let lw = l.offsetWidth;
+ debug("lw", l.textContent, lw);
+ if (lw > labelWidth) labelWidth = lw;
+ }
+
+ debug(`Preset: %s Label: %s`, presetWidth, labelWidth);
+ labelWidth += 16;
+ if (presetWidth < labelWidth) {
+ for (let ss of document.styleSheets) {
+ if (ss.href.endsWith("/ui.css")) {
+ for (let r of ss.cssRules) {
+ if (/input\.preset:checked.*min-width:/.test(r.cssText)) {
+ r.style.minWidth = (labelWidth) + "px";
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ sizer.remove();
+
+ }, 100);
+ UI.Sites.correctSize = () => {}; // just once, please!
+ }
+
+ allSiteRows() {
+ return this.table.querySelectorAll("tr.site");
+ }
+ clear() {
+ debug("Clearing list", this.table);
+
+ this.template = document.createElement("template");
+ this.template.innerHTML = TEMPLATE;
+ this.fragment = this.template.content;
+ this.table = this.fragment.querySelector("table.sites");
+ this.rowTemplate = this.initRow();
+
+ for (let r of this.allSiteRows()) {
+ r.parentNode.removeChild(r);
+ }
+ this.customize(null);
+ this.sitesCount = 0;
+ }
+
+ siteNeeds(site, type) {
+ let siteTypes = this.typesMap && this.typesMap.get(site);
+ return !!siteTypes && siteTypes.has(type);
+ }
+
+ handleEvent(ev) {
+ let target = ev.target;
+ let customizer = target.closest(".customizer");
+ let row = customizer ? customizer.parentNode.querySelector("tr.customizing") : target.closest("tr.site");
+ if (!row) return;
+ row.temp2perm = false;
+ let isTemp = target.matches("input.temp");
+ let preset = target.matches("input.preset") ? target
+ : customizer || isTemp ? row.querySelector("input.preset:checked")
+ : target.closest("input.preset");
+ debug("%s target %o\n\trow %s, perms %o\npreset %s %s",
+ ev.type,
+ target, row && row.siteMatch, row && row.perms,
+ preset && preset.value, preset && preset.checked);
+
+ if (!preset) {
+ if (target.matches("input.https-only") && ev.type === "change") {
+ this.toggleSecure(row, target.checked);
+ fireOnChange(this, row);
+ } else if (target.matches(".domain")) {
+ UI.openSiteInfo(row.domain);
+ }
+ return;
+ }
+
+ let policy = UI.policy;
+ let {siteMatch, contextMatch, perms} = row;
+ let presetValue = preset.value;
+ let policyPreset = presetValue.startsWith("T_") ? policy[presetValue.substring(2)].tempTwin : policy[presetValue];
+
+ if (policyPreset) {
+ if (row.perms !== policyPreset) {
+ row.temp2perm = row.perms && policyPreset.tempTwin === row.perms;
+ row.perms = policyPreset;
+ }
+ }
+
+
+ let isCap = customizer && target.matches(".cap");
+ let tempToggle = preset.parentNode.querySelector("input.temp");
+
+ if (ev.type === "change") {
+ if (preset.checked) {
+ row.dataset.preset = preset.value;
+ }
+ if (isCap) {
+ perms.set(target.value, target.checked);
+ } else if (policyPreset) {
+ if (tempToggle && tempToggle.checked) {
+ policyPreset = policyPreset.tempTwin;
+ }
+ row.contextMatch = null;
+ row.perms = policyPreset;
+ delete row._customPerms;
+ debug("Site match", siteMatch);
+ if (siteMatch) {
+ policy.set(siteMatch, policyPreset);
+ } else {
+ this.customize(policyPreset, preset, row);
+ }
+
+ } else if (preset.value === "CUSTOM") {
+ if (isTemp) {
+ row.perms.temp = target.checked;
+ } else {
+ let temp = row.perms.temp;
+ tempToggle.checked = temp;
+ let perms = row._customPerms ||
+ (row._customPerms = new Permissions(new Set(row.perms.capabilities), temp));
+ row.perms = perms;
+ policy.set(siteMatch, perms);
+ this.customize(perms, preset, row);
+ }
+ }
+ fireOnChange(this, row);
+ } else if (!(isCap || isTemp) && ev.type === "click") {
+ this.customize(row.perms, preset, row);
+ }
+ }
+
+ customize(perms, preset, row) {
+ debug("Customize preset %s (%o) - Dirty: %s", preset && preset.value, perms, this.dirty);
+ for(let r of this.table.querySelectorAll("tr.customizing")) {
+ r.classList.toggle("customizing", false);
+ }
+ let customizer = this.rowTemplate._customizer;
+ customizer.classList.toggle("closed", true);
+
+ if (!(perms && row && preset &&
+ row.dataset.preset === preset.value &&
+ this.presets[preset.value] &&
+ preset !== customizer._preset)) {
+ delete customizer._preset;
+ return;
+ }
+
+ customizer._preset = preset;
+ row.classList.toggle("customizing", true);
+ let immutable = Permissions.IMMUTABLE[preset.value] || {};
+ for (let input of customizer.querySelectorAll("input")) {
+ let type = input.value;
+ if (type in immutable) {
+ input.disabled = true;
+ input.checked = immutable[type];
+ } else {
+ input.checked = perms.allowing(type);
+ input.disabled = false;
+ }
+ input.parentNode.classList.toggle("needed", this.siteNeeds(row._site, type));
+ row.parentNode.insertBefore(customizer, row.nextElementSibling);
+ customizer.classList.toggle("closed", false);
+ customizer.onkeydown = e => {
+ switch(e.keyCode) {
+ case 38:
+ case 8:
+ e.preventDefault();
+ this.onkeydown = null;
+ this.customize(null);
+ preset.focus();
+ return false;
+ }
+ }
+ window.setTimeout(() => customizer.querySelector("input").focus(), 50);
+ }
+ }
+
+ render(sites = this.sites, sorter = this.sorter) {
+ let parentNode = this.parentNode;
+ debug("Rendering %o inside %o", sites, parentNode);
+ if (sites) this._populate(sites, sorter);
+ parentNode.innerHTML = "";
+ parentNode.appendChild(this.fragment);
+ let root = parentNode.querySelector("table.sites");
+ debug("Wiring", root);
+ if (!root.wiredBy) {
+ root.addEventListener("click", this, true);
+ root.addEventListener("change", this, true);
+ root.wiredBy = this;
+ }
+ return root;
+ }
+
+ _populate(sites, sorter) {
+ this.clear();
+ if (sites instanceof Sites) {
+ for (let [site, perms] of sites) {
+ this.append(site, site, perms);
+ }
+ } else {
+ for (let site of sites) {
+ let context = null;
+ if (site.site) {
+ site = site.site;
+ context = site.context;
+ }
+ let {siteMatch, perms, contextMatch} = UI.policy.get(site, context);
+ this.append(site, siteMatch, perms, contextMatch);
+ }
+ this.sites = sites;
+ }
+ this.sort(sorter);
+ window.setTimeout(() => this.focus(), 50);
+ }
+
+ focus() {
+ let firstPreset = this.table.querySelector("input.preset:checked");
+ if (firstPreset) firstPreset.focus();
+ }
+
+ sort(sorter = this.sorter) {
+ if (this.mainDomain) {
+ let md = this.mainDomain;
+ let wrappedCompare = sorter;
+ sorter = (a, b) => {
+ let x = a.domain, y = b.domain;
+ if (x === md) {
+ if (y !== md) {
+ return -1;
+ }
+ } else if (y === md) {
+ return 1;
+ }
+ return wrappedCompare(a, b);
+ }
+ }
+ let rows = [...this.allSiteRows()].sort(sorter);
+ if (this.mainSite) {
+ let mainLabel = "." + this.mainDomain;
+ let topIdx = rows.findIndex(r => r._label === mainLabel);
+ if (topIdx === -1) rows.findIndex(r => r._site === this.mainSite);
+ if (topIdx !== -1) {
+ // move the row to the top
+ let topRow = rows.splice(topIdx, 1)[0];
+ rows.unshift(topRow);
+ topRow.classList.toggle("main", true);
+ }
+ }
+ this.clear();
+ for (let row of rows) this.table.appendChild(row);
+ this.table.appendChild(this.rowTemplate._customizer);
+ }
+
+ sorter(a, b) {
+ return compareBy("domain", a, b) || compareBy("_label", a, b);
+ }
+
+ async tempTrustAll() {
+ let {policy} = UI;
+ let changed = 0;
+ for (let row of this.allSiteRows()) {
+ if (row._preset === "DEFAULT") {
+ policy.set(row._site, policy.TRUSTED.tempTwin);
+ changed++;
+ }
+ }
+ if (changed && UI.isDirty(true)) {
+ await UI.updateSettings({policy, reloadAffected: true});
+ }
+ return changed;
+ }
+
+ createSiteRow(site, siteMatch, perms, contextMatch = null, sitesCount = this.sitesCount++) {
+ debug("Creating row for site: %s, matching %s / %s, %o", site, siteMatch, contextMatch, perms);
+
+ let row = this.rowTemplate.cloneNode(true);
+ row.sitesCount = sitesCount;
+ let url;
+ try {
+ url = new URL(site);
+ } catch (e) {
+ if (/^(\w+:)\/*$/.test(site)) {
+ url = {protocol: RegExp.$1, hostname: "", origin: site, pathname:""};
+ let hostname = Sites.toExternal(url.hostname);
+ debug("Lonely %o", url, Sites.isSecureDomainKey(siteMatch) || !hostname && url.protocol === "https:");
+ } else {
+ let protocol = Sites.isSecureDomainKey(site) ? "https:" : "http:";
+ let hostname = Sites.toggleSecureDomainKey(site, false);
+ url = {protocol, hostname, origin: `${protocol}://${site}`, pathname: "/"};
+ }
+ }
+
+ let hostname = Sites.toExternal(url.hostname);
+ let domain = tld.getDomain(hostname);
+
+ if (!siteMatch) {
+ siteMatch = site;
+ }
+ let secure = Sites.isSecureDomainKey(siteMatch);
+ let keyStyle = secure ? "secure"
+ : !domain || /^\w+:/.test(siteMatch) ?
+ (url.protocol === "https:" ? "full" : "unsafe")
+ : domain === hostname ? "domain" : "host";
+
+ let urlContainer = row.querySelector(".url");
+ urlContainer.dataset.key = keyStyle;
+ row._site = site;
+
+ row.siteMatch = siteMatch;
+ row.contextMatch = contextMatch;
+ row.perms = perms;
+ row.domain = domain || siteMatch;
+ if (domain) { // "normal" URL
+ let justDomain = hostname === domain;
+ let domainEntry = secure || domain === site;
+ row._label = domainEntry ? "." + domain : site;
+ row.querySelector(".protocol").textContent = `${url.protocol}//`;
+ row.querySelector(".sub").textContent = justDomain ?
+ (keyStyle === "full" || keyStyle == "unsafe"
+ ? "" : "…")
+ : hostname.substring(0, hostname.length - domain.length);
+
+ row.querySelector(".domain").textContent = domain;
+ row.querySelector(".path").textContent = siteMatch.length > url.origin.length ? url.pathname : "";
+ } else {
+ row._label = siteMatch;
+ urlContainer.querySelector(".full-address").textContent = siteMatch;
+ }
+ let httpsOnly = row.querySelector("input.https-only");
+ httpsOnly.checked = keyStyle === "full" || keyStyle === "secure";
+
+ let presets = row.querySelectorAll("input.preset");
+ let idSuffix = `-${this.uiCount}-${sitesCount}`;
+ for (let p of presets) {
+ p.id = `${p.value}${idSuffix}`;
+ p.name = `preset${idSuffix}`;
+ let label = p.nextElementSibling;
+ label.setAttribute("for", p.id);
+ let temp = p.parentNode.querySelector("input.temp");
+ if (temp) {
+ temp.id = `temp-${p.id}`;
+ label = temp.nextElementSibling;
+ label.setAttribute("for", temp.id);
+ }
+ }
+ let policy = UI.policy;
+
+ let presetName = "CUSTOM";
+ for (let p of ["TRUSTED", "UNTRUSTED", "DEFAULT"]) {
+ let preset = policy[p];
+ switch (perms) {
+ case preset:
+ presetName = p;
+ break;
+ case preset.tempTwin:
+ presetName = `T_${p}`;
+ if (!presetName in UI.presets) {
+ presetName = p;
+ }
+ break;
+ }
+ }
+ let tempFirst = true; // TODO: make it a preference
+ let unsafeMatch = keyStyle !== "secure" && keyStyle !== "full";
+ if (presetName === "DEFAULT" && (tempFirst || unsafeMatch)) {
+ // prioritize temporary privileges over permanent
+ for (let p of TEMP_PRESETS) {
+ if (p in this.presets && (unsafeMatch || tempFirst && p === "TRUSTED")) {
+ row.querySelector(`.presets input[value="${p}"]`).parentNode.querySelector("input.temp").checked = true;
+ perms = policy.TRUSTED.tempTwin;
+ }
+ }
+ }
+ let preset = row.querySelector(`.presets input[value="${presetName}"]`);
+ if (!preset) {
+ debug(`Preset %s not found in %s!`, presetName, row.innerHTML);
+ } else {
+ preset.checked = true;
+ row.dataset.preset = row._preset = presetName;
+ if (TEMP_PRESETS.includes(presetName)) {
+ let temp = preset.parentNode.querySelector("input.temp");
+ if (temp) {
+ temp.checked = perms.temp;
+ }
+ }
+ }
+ return row;
+ }
+
+ append(site, siteMatch, perms, contextMatch) {
+ this.table.appendChild(this.createSiteRow(...arguments));
+ }
+
+ toggleSecure(row, secure = !!row.querySelector("https-only:checked")) {
+ this.customize(null);
+ let site = row.siteMatch;
+ site = site.replace(/^https?:/, secure ? "https:" : "http:");
+ if (site === row.siteMatch) {
+ site = Sites.toggleSecureDomainKey(site, secure);
+ }
+ if (site !== row.siteMatch) {
+ let {policy} = UI;
+ policy.set(row.siteMatch, policy.DEFAULT);
+ policy.set(site, row.perms);
+ for(let r of this.allSiteRows()) {
+ if (r !== row && r.siteMatch === site && r.contextMatch === row.contextMatch) {
+ r.parentNode.removeChild(r);
+ }
+ }
+ let newRow = this.createSiteRow(site, site, row.perms, row.contextMatch, row.sitesCount);
+ row.parentNode.replaceChild(newRow, row);
+ }
+ }
+
+ highlight(key) {
+ key = Sites.toExternal(key);
+ for (let r of this.allSiteRows()) {
+ if (r.querySelector(".full-address").textContent.trim().includes(key)) {
+ let url = r.lastElementChild;
+ url.style.transition = r.style.transition = "none";
+ r.style.backgroundColor = "#850";
+ url.style.transform = "scale(2)";
+ r.querySelector("input.preset:checked").focus();
+ window.setTimeout(() => {
+ r.style.transition = "1s background-color";
+ url.style.transition = "1s transform";
+ r.style.backgroundColor = "";
+ url.style.transform = "none";
+ r.scrollIntoView();
+ }, 50);
+ }
+ }
+ }
+
+ filterSites(key) {
+ key = Sites.toExternal(key);
+ for (let r of this.allSiteRows()) {
+ if (r.querySelector(".full-address").textContent.trim().includes(key)) {
+ r.style.display = "";
+ } else {
+ r.style.display = "none";
+ }
+ }
+ }
+ }
+
+ return UI;
+})();
diff --git a/src/ui/whirlpool.css b/src/ui/whirlpool.css
index 0e2147a9..6f24c040 100644
--- a/src/ui/whirlpool.css
+++ b/src/ui/whirlpool.css
@@ -1,45 +1,45 @@
-
-.cssload-container{
- position:relative;
-}
-
-.cssload-whirlpool,
-.cssload-whirlpool::before,
-.cssload-whirlpool::after {
- position: absolute;
- top: 50%;
- left: 50%;
- border: 1px solid rgb(204,204,204);
- border-left-color: rgb(0,0,0);
- border-radius: 974px;
-}
-
-.cssload-whirlpool {
- margin: -24px 0 0 -24px;
- height: 49px;
- width: 49px;
- animation: cssload-rotate 1150ms linear infinite;
-}
-
-.cssload-whirlpool::before {
- content: "";
- margin: -22px 0 0 -22px;
- height: 43px;
- width: 43px;
- animation: cssload-rotate 1150ms linear infinite;
-}
-
-.cssload-whirlpool::after {
- content: "";
- margin: -28px 0 0 -28px;
- height: 55px;
- width: 55px;
- animation: cssload-rotate 2300ms linear infinite;
-}
-
-
-@keyframes cssload-rotate {
- 100% {
- transform: rotate(360deg);
- }
-}
+
+.cssload-container{
+ position:relative;
+}
+
+.cssload-whirlpool,
+.cssload-whirlpool::before,
+.cssload-whirlpool::after {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ border: 1px solid rgb(204,204,204);
+ border-left-color: rgb(0,0,0);
+ border-radius: 974px;
+}
+
+.cssload-whirlpool {
+ margin: -24px 0 0 -24px;
+ height: 49px;
+ width: 49px;
+ animation: cssload-rotate 1150ms linear infinite;
+}
+
+.cssload-whirlpool::before {
+ content: "";
+ margin: -22px 0 0 -22px;
+ height: 43px;
+ width: 43px;
+ animation: cssload-rotate 1150ms linear infinite;
+}
+
+.cssload-whirlpool::after {
+ content: "";
+ margin: -28px 0 0 -28px;
+ height: 55px;
+ width: 55px;
+ animation: cssload-rotate 2300ms linear infinite;
+}
+
+
+@keyframes cssload-rotate {
+ 100% {
+ transform: rotate(360deg);
+ }
+}