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_CustomizePresets__ -
-
-
-
- -

-
-
-
- -
-
-
-
-
-
-
-
- -

-
-
- - - - -
-
- - - - -
-
- - - - -
-
- - - - -
-
- -

-
-
- - - (__MSG_XssFaq__) - - -
-
- -
- -
-
- -
-
- -
- -
-
-
-
- - - - + + + + +NoScript Settings + + + + + + + + + + + + + +
+ +
+ +
+ +

+
+
+
+ + + + + + + +
+ +
+ __MSG_CustomizePresets__ +
+
+
+
+ +

+
+
+
+ +
+
+
+
+
+
+
+
+ +

+
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ +

+
+
+ + + (__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 - - - - - - - - - -
-
- __MSG_Close__ - __MSG_Reload__ - __MSG_Options__ -
- -
__MSG_Hider__
- -
-
- - - __MSG_TempTrustPage__ - __MSG_RevokeTemp__ -
- -
- - - - -
-
-
-
- -
-
- - - + + + + + +NoScript Settings + + + + + + + + + +
+
+ __MSG_Close__ + __MSG_Reload__ + __MSG_Options__ +
+ +
__MSG_Hider__
+ +
+
+ + + __MSG_TempTrustPage__ + __MSG_RevokeTemp__ +
+ +
+ + + + +
+
+
+
+ +
+
+ + + 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 = ` - - - - - - - - - - - - - -
- - - - - - - - https://www.noscript.net - -
-
-
- - - - -
-
-
- `; - - 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 = ` + + + + + + + + + + + + + +
+ + + + + + + + https://www.noscript.net + +
+
+
+ + + + +
+
+
+ `; + + 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); + } +}